From de4e189c86b35fc7a988bfc04d790587f8b27041 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Mon, 25 Jun 2018 15:44:12 -0700 Subject: [PATCH 01/59] Update crosshair on mousemove --- ui/src/logs/components/LogViewerChart.tsx | 4 ++-- ui/src/shared/components/Dygraph.tsx | 11 +++-------- ui/src/shared/components/LineGraph.js | 4 +--- ui/src/shared/components/RefreshingGraph.js | 8 ++------ ui/src/shared/components/TableGraph.tsx | 6 +++++- 5 files changed, 13 insertions(+), 20 deletions(-) diff --git a/ui/src/logs/components/LogViewerChart.tsx b/ui/src/logs/components/LogViewerChart.tsx index 02831a0265..9b65198925 100644 --- a/ui/src/logs/components/LogViewerChart.tsx +++ b/ui/src/logs/components/LogViewerChart.tsx @@ -18,10 +18,10 @@ class LogViewerChart extends PureComponent { onZoom={onZoom} queries={[]} data={data} - displayOptions={{animatedZooms: false}} - setResolution={this.setResolution} isBarGraph={true} timeRange={timeRange} + displayOptions={{animatedZooms: false}} + setResolution={this.setResolution} colors={DEFAULT_LINE_COLORS} /> ) diff --git a/ui/src/shared/components/Dygraph.tsx b/ui/src/shared/components/Dygraph.tsx index 271d3cd687..7b968e42e6 100644 --- a/ui/src/shared/components/Dygraph.tsx +++ b/ui/src/shared/components/Dygraph.tsx @@ -148,7 +148,7 @@ class Dygraph extends Component { zoomCallback: (lower: number, upper: number) => this.handleZoom(lower, upper), drawCallback: () => this.handleDraw(), - highlightCircleSize: 0, + highlightCircleSize: 3, } if (isBarGraph) { @@ -242,7 +242,7 @@ class Dygraph extends Component { const {staticLegend, cellID} = this.props return ( -
+
{this.dygraph && (
{this.areAnnotationsVisible && ( @@ -265,11 +265,7 @@ class Dygraph extends Component { />
)} -
+
{staticLegend && ( { className="dygraph-child-container" ref={this.graphRef} style={this.dygraphStyle} - onMouseEnter={this.handleShowLegend} /> { ) ) } + @ErrorHandlingWith(InvalidData) class LineGraph extends Component { constructor(props) { @@ -60,7 +61,6 @@ class LineGraph extends Component { cellID, onZoom, queries, - hoverTime, timeRange, cellHeight, ruleValues, @@ -120,7 +120,6 @@ class LineGraph extends Component { labels={labels} queries={queries} options={options} - hoverTime={hoverTime} timeRange={timeRange} isBarGraph={isBarGraph} timeSeries={timeSeries} @@ -182,7 +181,6 @@ LineGraph.propTypes = { label: string, }), }), - hoverTime: string, handleSetHoverTime: func, title: string, isFetchingInitially: bool, diff --git a/ui/src/shared/components/RefreshingGraph.js b/ui/src/shared/components/RefreshingGraph.js index e98aefd32e..8a8b4e7aef 100644 --- a/ui/src/shared/components/RefreshingGraph.js +++ b/ui/src/shared/components/RefreshingGraph.js @@ -31,7 +31,6 @@ const RefreshingGraph = ({ onZoom, cellID, queries, - hoverTime, tableOptions, templates, timeRange, @@ -106,7 +105,7 @@ const RefreshingGraph = ({ cellID={cellID} colors={colors} inView={inView} - hoverTime={hoverTime} + isInCEO={isInCEO} key={manualRefresh} queries={queries} templates={templates} @@ -120,7 +119,6 @@ const RefreshingGraph = ({ resizerTopHeight={resizerTopHeight} grabDataForDownload={grabDataForDownload} handleSetHoverTime={handleSetHoverTime} - isInCEO={isInCEO} onSetResolution={onSetResolution} /> ) @@ -199,7 +197,6 @@ RefreshingGraph.propTypes = { isEnforced: bool.isRequired, digits: number.isRequired, }).isRequired, - hoverTime: string.isRequired, handleSetHoverTime: func.isRequired, isInCEO: bool, onSetResolution: func, @@ -213,9 +210,8 @@ RefreshingGraph.defaultProps = { decimalPlaces: DEFAULT_DECIMAL_PLACES, } -const mapStateToProps = ({dashboardUI, annotations: {mode}}) => ({ +const mapStateToProps = ({annotations: {mode}}) => ({ mode, - hoverTime: dashboardUI.hoverTime, }) const mapDispatchToProps = dispatch => ({ diff --git a/ui/src/shared/components/TableGraph.tsx b/ui/src/shared/components/TableGraph.tsx index 4b2a2c48c6..ccd85fb911 100644 --- a/ui/src/shared/components/TableGraph.tsx +++ b/ui/src/shared/components/TableGraph.tsx @@ -589,8 +589,12 @@ class TableGraph extends Component { } } +const mstp = ({dashboardUI}) => ({ + hoverTime: dashboardUI.hoverTime, +}) + const mapDispatchToProps = dispatch => ({ handleUpdateFieldOptions: bindActionCreators(updateFieldOptions, dispatch), }) -export default connect(null, mapDispatchToProps)(TableGraph) +export default connect(mstp, mapDispatchToProps)(TableGraph) From 16368a6bd943110568828b86f713a289e4963c9b Mon Sep 17 00:00:00 2001 From: Alirie Gray Date: Tue, 26 Jun 2018 17:12:00 -0700 Subject: [PATCH 02/59] Add a swagger definition for UI config settings and API Routes Specifically, this adds a definition for getting and updating the log viewer UI Settings, i.e. severityColors, columns, and severityColumnFormat. Co-authored-by: Jared Scheib --- server/swagger.json | 177 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 4 deletions(-) diff --git a/server/swagger.json b/server/swagger.json index ea89a0b958..fb6834a530 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -2864,6 +2864,112 @@ } } } + }, + "/chronograf/v1/config/ui": { + "get": { + "tags": ["config", "ui"], + "summary": "Returns all UI settings", + "description": "All UI section settings", + "responses": { + "200": { + "description": "Returns an object with all UI settings for the app", + "schema": { + "$ref": "#/definitions/UIConfig" + } + }, + "default": { + "description": "Unexpected internal server error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, + "/chronograf/v1/config/ui/:section": { + "get": { + "tags": ["config", "ui"], + "summary": "Returns the UI settings for a specific section of the app", + "description": "All UI settings for a specific section of the app", + "responses": { + "200": { + "description": "Returns an object with the UI settings for a section of the app", + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/LogViewerUISettings" + } + ] + } + }, + "404": { + "description": "Could not find requested section", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "default": { + "description": "Unexpected internal server error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + }, + "put": { + "tags": ["config", "ui"], + "summary": "Updates the UI settings for a specific section of the app", + "description": "Updates UI settings for a specific section of the app", + "parameters": [ + { + "name": "section", + "in": "path", + "type": "string", + "description": "Section name for the target UI section settings", + "required": true + }, + { + "name": { + "enum": ["logViewer"] + }, + "in": "body", + "description": + "UI section configuration update parameters", + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/LogViewerUISettings" + } + ] + }, + "required": true + } + ], + "responses": { + "200": { + "description": "Returns an object with the updated UI settings for a specific section", + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/LogViewerUISettings" + } + ] + } + }, + "404": { + "description": "Could not find requested section", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "default": { + "description": "Unexpected internal server error", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } } }, "definitions": { @@ -5030,27 +5136,90 @@ } } }, + "SeverityColor": { + "type": "object", + "description": + "Color defines an encoding of a data value into color space", + "properties": { + "id": { + "description": "ID is the unique id of the cell color", + "readOnly": true, + "type": "string" + }, + "type": { + "description": "Type is how the color is used", + "type": "string", + "enum": ["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"] + }, + "hex": { + "description": "Hex is the hex number of the color", + "type": "string", + "maxLength": 7, + "minLength": 7 + }, + "name": { + "description": "Name is the user-facing name of the hex color", + "type": "string" + } + } + }, "RenamableField": { "description": - "renamableField describes a field that can be renamed and made visible or invisible", + "Describes a field that can be renamed and made visible or invisible", "type": "object", "properties": { "internalName": { - "description": "internalName is the calculated name of a field", + "description": "This is the calculated name of a field", + "readOnly": true, "type": "string" }, "displayName": { "description": - "displayName is the name that a field is renamed to by the user", + "This is the name that a field is renamed to by the user", "type": "string" }, "visible": { "description": - "visible indicates whether this field should be visible on the table", + "Indicates whether this field should be visible on the table", "type": "boolean" } } }, + "LogViewerUISettings": { + "description": "Contains the settings for the log viewer page UI", + "type": "object", + "required": ["columns", "severityColors", "severityColumnFormat"], + "properties": { + "columns": { + "description": "Defines the order, names, and visibility of columns in the log viewer table", + "type": "array", + "items": { + "$ref": "#/definitions/RenamableField" + } + }, + "severityColors": { + "description": "Defines the name and color associated with log severity levels", + "type": "array", + "items": { + "$ref": "#/definitions/SeverityColor" + } + }, + "severityColumnFormat": { + "description": "Describes the format for displaying the log severity to the user", + "type": "string", + "enum": ["dot", "text", "dot-text"] + } + } + }, + "UIConfig": { + "description": "UIConfig describes UI settings for chronograf", + "type": "object", + "properties": { + "logViewer": { + "$ref": "#/definitions/LogViewerUISettings" + } + } + }, "Routes": { "type": "object", "properties": { From 704807f683ebc895441e40fadc14b4819bfaff68 Mon Sep 17 00:00:00 2001 From: Alirie Gray Date: Wed, 27 Jun 2018 15:07:17 -0700 Subject: [PATCH 03/59] Simplify log viewer UI config models in Swagger Co-authored-by: Jared Scheib --- server/swagger.json | 68 ++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/server/swagger.json b/server/swagger.json index fb6834a530..8e61445313 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -5136,33 +5136,6 @@ } } }, - "SeverityColor": { - "type": "object", - "description": - "Color defines an encoding of a data value into color space", - "properties": { - "id": { - "description": "ID is the unique id of the cell color", - "readOnly": true, - "type": "string" - }, - "type": { - "description": "Type is how the color is used", - "type": "string", - "enum": ["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"] - }, - "hex": { - "description": "Hex is the hex number of the color", - "type": "string", - "maxLength": 7, - "minLength": 7 - }, - "name": { - "description": "Name is the user-facing name of the hex color", - "type": "string" - } - } - }, "RenamableField": { "description": "Describes a field that can be renamed and made visible or invisible", @@ -5188,7 +5161,7 @@ "LogViewerUISettings": { "description": "Contains the settings for the log viewer page UI", "type": "object", - "required": ["columns", "severityColors", "severityColumnFormat"], + "required": ["columns", "columnOptions"], "properties": { "columns": { "description": "Defines the order, names, and visibility of columns in the log viewer table", @@ -5197,17 +5170,36 @@ "$ref": "#/definitions/RenamableField" } }, - "severityColors": { - "description": "Defines the name and color associated with log severity levels", - "type": "array", - "items": { - "$ref": "#/definitions/SeverityColor" + "columnOptions": { + "type": "object", + "required": ["severity"], + "properties": { + "severity": { + "type": "object", + "required": ["displayFormat", "colors"], + "properties": { + "displayFormat": { + "type": "string" + }, + "colors": { + "description": "Defines the display colors for each severity level", + "type": "array", + "items": { + "type": "object", + "required": ["name", "value"], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + } + } + } } - }, - "severityColumnFormat": { - "description": "Describes the format for displaying the log severity to the user", - "type": "string", - "enum": ["dot", "text", "dot-text"] } } }, From 96f905aacb4157400ca613011cf4a9de7469c181 Mon Sep 17 00:00:00 2001 From: Alirie Gray Date: Wed, 27 Jun 2018 15:17:14 -0700 Subject: [PATCH 04/59] Simplify Swagger config API & add AuthConfig definition Co-Authored-By: Jared Scheib --- server/swagger.json | 66 ++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/server/swagger.json b/server/swagger.json index 8e61445313..fff80a19c2 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -2865,39 +2865,21 @@ } } }, - "/chronograf/v1/config/ui": { + "/chronograf/v1/config/:section": { "get": { - "tags": ["config", "ui"], - "summary": "Returns all UI settings", - "description": "All UI section settings", + "tags": ["config"], + "summary": "Returns the settings for a specific section of the app", + "description": "All settings for a specific section of the app", "responses": { "200": { - "description": "Returns an object with all UI settings for the app", - "schema": { - "$ref": "#/definitions/UIConfig" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/chronograf/v1/config/ui/:section": { - "get": { - "tags": ["config", "ui"], - "summary": "Returns the UI settings for a specific section of the app", - "description": "All UI settings for a specific section of the app", - "responses": { - "200": { - "description": "Returns an object with the UI settings for a section of the app", + "description": "Returns an object with the settings for a section of the app", "schema": { "oneOf": [ { - "$ref": "#/definitions/LogViewerUISettings" + "$ref": "#/definitions/LogViewerUIConfig" + }, + { + "$ref": "#/definitions/AuthConfig" } ] } @@ -2917,28 +2899,31 @@ } }, "put": { - "tags": ["config", "ui"], - "summary": "Updates the UI settings for a specific section of the app", - "description": "Updates UI settings for a specific section of the app", + "tags": ["config"], + "summary": "Updates the settings for a specific section of the app", + "description": "Updates settings for a specific section of the app", "parameters": [ { "name": "section", "in": "path", "type": "string", - "description": "Section name for the target UI section settings", + "description": "Section name for the target section settings", "required": true }, { "name": { - "enum": ["logViewer"] + "enum": ["logViewer", "auth"] }, "in": "body", "description": - "UI section configuration update parameters", + "Section configuration update parameters", "schema": { "oneOf": [ { - "$ref": "#/definitions/LogViewerUISettings" + "$ref": "#/definitions/LogViewerUIConfig" + }, + { + "$ref": "#/definitions/AuthConfig" } ] }, @@ -2951,7 +2936,7 @@ "schema": { "oneOf": [ { - "$ref": "#/definitions/LogViewerUISettings" + "$ref": "#/definitions/LogViewerUIConfig" } ] } @@ -5158,7 +5143,7 @@ } } }, - "LogViewerUISettings": { + "LogViewerUIConfig": { "description": "Contains the settings for the log viewer page UI", "type": "object", "required": ["columns", "columnOptions"], @@ -5203,12 +5188,13 @@ } } }, - "UIConfig": { - "description": "UIConfig describes UI settings for chronograf", + "AuthConfig": { "type": "object", + "required": ["superAdminNewUsers"], "properties": { - "logViewer": { - "$ref": "#/definitions/LogViewerUISettings" + "superAdminNewUsers": { + "type": "boolean", + "default": true } } }, From eef6738bf3d3ff6a4a8dfc69b958f9f10d51d491 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 27 Jun 2018 16:06:05 -0700 Subject: [PATCH 05/59] Fix static legend state loop --- ui/src/shared/components/Crosshair.tsx | 7 +++---- ui/src/shared/components/Dygraph.tsx | 9 ++++----- ui/src/shared/components/StaticLegend.js | 18 ++++++++++++------ ui/src/style/components/crosshairs.scss | 15 +++++---------- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/ui/src/shared/components/Crosshair.tsx b/ui/src/shared/components/Crosshair.tsx index 2799554143..d25fc48c26 100644 --- a/ui/src/shared/components/Crosshair.tsx +++ b/ui/src/shared/components/Crosshair.tsx @@ -16,7 +16,7 @@ interface Props { class Crosshair extends PureComponent { public render() { if (!this.isVisible) { - return null + return
} return ( @@ -26,7 +26,6 @@ class Crosshair extends PureComponent { style={{ left: this.crosshairLeft, height: this.crosshairHeight, - width: '1px', }} />
@@ -47,10 +46,10 @@ class Crosshair extends PureComponent { return isValidHoverTime && hoverTime !== 0 && _.isFinite(hoverTime) } - private get crosshairLeft(): number { + private get crosshairLeft(): string { const {dygraph, hoverTime} = this.props const cursorOffset = 16 - return dygraph.toDomXCoord(hoverTime) + cursorOffset + return `${dygraph.toDomXCoord(hoverTime) + cursorOffset}px` } private get crosshairHeight(): string { diff --git a/ui/src/shared/components/Dygraph.tsx b/ui/src/shared/components/Dygraph.tsx index 7b968e42e6..9816a28869 100644 --- a/ui/src/shared/components/Dygraph.tsx +++ b/ui/src/shared/components/Dygraph.tsx @@ -261,7 +261,7 @@ class Dygraph extends Component { />
)} @@ -270,9 +270,8 @@ class Dygraph extends Component { )} {this.nestedGraph && @@ -456,7 +455,7 @@ class Dygraph extends Component { return nanoDate.toISOString() } - private handleReceiveStaticLegendHeight = (staticLegendHeight: number) => { + private handleUpdateStaticLegendHeight = (staticLegendHeight: number) => { this.setState({staticLegendHeight}) } } diff --git a/ui/src/shared/components/StaticLegend.js b/ui/src/shared/components/StaticLegend.js index 5541ae9ebc..a085caae93 100644 --- a/ui/src/shared/components/StaticLegend.js +++ b/ui/src/shared/components/StaticLegend.js @@ -29,16 +29,21 @@ class StaticLegend extends Component { componentDidMount = () => { const {height} = this.staticLegendRef.getBoundingClientRect() - this.props.handleReceiveStaticLegendHeight(height) + this.props.onUpdateHeight(height) } - componentDidUpdate = () => { + componentDidUpdate = prevProps => { const {height} = this.staticLegendRef.getBoundingClientRect() - this.props.handleReceiveStaticLegendHeight(height) + + if (prevProps.height === height) { + return + } + + this.props.onUpdateHeight(height) } componentWillUnmount = () => { - this.props.handleReceiveStaticLegendHeight(null) + this.props.onUpdateHeight(0) } handleClick = i => e => { @@ -96,12 +101,13 @@ class StaticLegend extends Component { } } -const {shape, func} = PropTypes +const {shape, func, number} = PropTypes StaticLegend.propTypes = { dygraphSeries: shape({}), dygraph: shape({}), - handleReceiveStaticLegendHeight: func.isRequired, + height: number.isRequired, + onUpdateHeight: func.isRequired, } export default StaticLegend diff --git a/ui/src/style/components/crosshairs.scss b/ui/src/style/components/crosshairs.scss index b10b2dcdf2..cb0d4189c6 100644 --- a/ui/src/style/components/crosshairs.scss +++ b/ui/src/style/components/crosshairs.scss @@ -3,17 +3,12 @@ ------------------------------------------------------------------------------ */ -%crosshair-styles { - position: absolute; - cursor: pointer; -} - .crosshair { - @extend %crosshair-styles; top: 0; - height: calc(100% - 20px); - width: 0.5px; - background-color: $g14-chromium; - pointer-events: none; + position: absolute; + width: 1px; z-index: 3; + background: linear-gradient(to bottom, fade-out($g14-chromium, 1) 0%,$g14-chromium 7%,$g14-chromium 93%,fade-out($g14-chromium, 1) 100%); + pointer-events: none; + min-height: 20px; } From de215570d2295a2af23ec7615253b9e52712e20d Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 27 Jun 2018 16:07:50 -0700 Subject: [PATCH 06/59] Use more performant means of positioning crosshair --- ui/src/shared/components/Crosshair.tsx | 4 ++-- ui/src/style/components/crosshairs.scss | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/src/shared/components/Crosshair.tsx b/ui/src/shared/components/Crosshair.tsx index d25fc48c26..7c72467fe9 100644 --- a/ui/src/shared/components/Crosshair.tsx +++ b/ui/src/shared/components/Crosshair.tsx @@ -24,7 +24,7 @@ class Crosshair extends PureComponent {
@@ -49,7 +49,7 @@ class Crosshair extends PureComponent { private get crosshairLeft(): string { const {dygraph, hoverTime} = this.props const cursorOffset = 16 - return `${dygraph.toDomXCoord(hoverTime) + cursorOffset}px` + return `translateX(${dygraph.toDomXCoord(hoverTime) + cursorOffset}px)` } private get crosshairHeight(): string { diff --git a/ui/src/style/components/crosshairs.scss b/ui/src/style/components/crosshairs.scss index cb0d4189c6..b2808398c0 100644 --- a/ui/src/style/components/crosshairs.scss +++ b/ui/src/style/components/crosshairs.scss @@ -4,6 +4,7 @@ */ .crosshair { + left: 0; top: 0; position: absolute; width: 1px; From 827b944162d13b1bf17ffbb360640d88cce8053d Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 27 Jun 2018 16:23:08 -0700 Subject: [PATCH 07/59] Use number instead of null --- ui/src/shared/components/Dygraph.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/shared/components/Dygraph.tsx b/ui/src/shared/components/Dygraph.tsx index 9816a28869..31d53d46cd 100644 --- a/ui/src/shared/components/Dygraph.tsx +++ b/ui/src/shared/components/Dygraph.tsx @@ -67,7 +67,7 @@ interface Props { } interface State { - staticLegendHeight: null | number + staticLegendHeight: number xAxisRange: [number, number] } @@ -102,7 +102,7 @@ class Dygraph extends Component { constructor(props: Props) { super(props) this.state = { - staticLegendHeight: null, + staticLegendHeight: 0, xAxisRange: [0, 0], } From 0d3276be62702faa4ea3394d5ee41b91f2072d16 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 27 Jun 2018 16:23:52 -0700 Subject: [PATCH 08/59] Position legend using hover time --- ui/src/shared/components/DygraphLegend.tsx | 9 +++++++-- ui/src/shared/graphs/helpers.ts | 17 ++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/ui/src/shared/components/DygraphLegend.tsx b/ui/src/shared/components/DygraphLegend.tsx index 86ab96e27d..339503f9d8 100644 --- a/ui/src/shared/components/DygraphLegend.tsx +++ b/ui/src/shared/components/DygraphLegend.tsx @@ -15,6 +15,7 @@ import {NO_CELL} from 'src/shared/constants' import {DygraphClass} from 'src/types' interface Props { + hoverTime: number dygraph: DygraphClass cellID: string onHide: () => void @@ -256,10 +257,13 @@ class DygraphLegend extends PureComponent { private get styles() { const { + dygraph, dygraph: {graphDiv}, + hoverTime, } = this.props - const {pageX} = this.state - return makeLegendStyles(graphDiv, this.legendRef, pageX) + + const legendPosition = dygraph.toDomXCoord(hoverTime) + return makeLegendStyles(graphDiv, this.legendRef, legendPosition) } } @@ -269,6 +273,7 @@ const mapDispatchToProps = { const mapStateToProps = ({dashboardUI}) => ({ activeCellID: dashboardUI.activeCellID, + hoverTime: +dashboardUI.hoverTime, }) export default connect(mapStateToProps, mapDispatchToProps)(DygraphLegend) diff --git a/ui/src/shared/graphs/helpers.ts b/ui/src/shared/graphs/helpers.ts index b9f34b4839..cd794abfa9 100644 --- a/ui/src/shared/graphs/helpers.ts +++ b/ui/src/shared/graphs/helpers.ts @@ -114,8 +114,8 @@ export const barPlotter = e => { } } -export const makeLegendStyles = (graph, legend, pageX) => { - if (!graph || !legend || pageX === null) { +export const makeLegendStyles = (graph, legend, hoverTimeX) => { + if (!graph || !legend || hoverTimeX === null) { return {} } @@ -131,21 +131,20 @@ export const makeLegendStyles = (graph, legend, pageX) => { const legendHeight = legendRect.height const screenHeight = window.innerHeight const legendMaxLeft = graphWidth - legendWidth / 2 - const trueGraphX = pageX - graphRect.left - let legendLeft = trueGraphX + let legendLeft = hoverTimeX // Enforcing max & min legend offsets - if (trueGraphX < legendWidth / 2) { + if (hoverTimeX < legendWidth / 2) { legendLeft = legendWidth / 2 - } else if (trueGraphX > legendMaxLeft) { + } else if (hoverTimeX > legendMaxLeft) { legendLeft = legendMaxLeft } // Disallow screen overflow of legend const isLegendBottomClipped = graphBottom + legendHeight > screenHeight const isLegendTopClipped = legendHeight > graphRect.top - chronografChromeSize - const willLegendFitLeft = pageX - chronografChromeSize > legendWidth + const willLegendFitLeft = hoverTimeX - chronografChromeSize > legendWidth let legendTop = graphHeight + 8 @@ -159,10 +158,10 @@ export const makeLegendStyles = (graph, legend, pageX) => { legendTop = 0 if (willLegendFitLeft) { - legendLeft = trueGraphX - legendWidth / 2 + legendLeft = hoverTimeX - legendWidth / 2 legendLeft -= 8 } else { - legendLeft = trueGraphX + legendWidth / 2 + legendLeft = hoverTimeX + legendWidth / 2 legendLeft += 32 } } From 46af7586c3efc5a05b686d382350bc3230fef854 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 27 Jun 2018 16:33:25 -0700 Subject: [PATCH 09/59] Don't move legend when mouse is in legend --- ui/src/shared/components/Dygraph.tsx | 14 ++++++++++++++ ui/src/shared/components/DygraphLegend.tsx | 6 +++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ui/src/shared/components/Dygraph.tsx b/ui/src/shared/components/Dygraph.tsx index 31d53d46cd..d785c227f3 100644 --- a/ui/src/shared/components/Dygraph.tsx +++ b/ui/src/shared/components/Dygraph.tsx @@ -69,6 +69,7 @@ interface Props { interface State { staticLegendHeight: number xAxisRange: [number, number] + isMouseInLegend: boolean } @ErrorHandling @@ -104,6 +105,7 @@ class Dygraph extends Component { this.state = { staticLegendHeight: 0, xAxisRange: [0, 0], + isMouseInLegend: false, } this.graphRef = React.createRef() @@ -258,6 +260,7 @@ class Dygraph extends Component { dygraph={this.dygraph} onHide={this.handleHideLegend} onShow={this.handleShowLegend} + onMouseEnter={this.handleMouseEnterLegend} /> { } private handleHideLegend = () => { + this.setState({isMouseInLegend: false}) this.props.handleSetHoverTime(NULL_HOVER_TIME) } private handleShowLegend = (e: MouseEvent): void => { + const {isMouseInLegend} = this.state + + if (isMouseInLegend) { + return + } + const newTime = this.eventToTimestamp(e) this.props.handleSetHoverTime(newTime) } @@ -458,6 +468,10 @@ class Dygraph extends Component { private handleUpdateStaticLegendHeight = (staticLegendHeight: number) => { this.setState({staticLegendHeight}) } + + private handleMouseEnterLegend = () => { + this.setState({isMouseInLegend: true}) + } } const mapStateToProps = ({annotations: {mode}}) => ({ diff --git a/ui/src/shared/components/DygraphLegend.tsx b/ui/src/shared/components/DygraphLegend.tsx index 339503f9d8..a26e8123e5 100644 --- a/ui/src/shared/components/DygraphLegend.tsx +++ b/ui/src/shared/components/DygraphLegend.tsx @@ -22,6 +22,7 @@ interface Props { onShow: (e: MouseEvent) => void activeCellID: string setActiveCell: (cellID: string) => void + onMouseEnter: () => void } interface LegendData { @@ -80,12 +81,14 @@ class DygraphLegend extends PureComponent { } public render() { + const {onMouseEnter} = this.props const {legend, filterText, isAscending, isFilterVisible} = this.state return (
(this.legendRef = el)} + onMouseEnter={onMouseEnter} onMouseLeave={this.handleHide} style={this.styles} > @@ -262,7 +265,8 @@ class DygraphLegend extends PureComponent { hoverTime, } = this.props - const legendPosition = dygraph.toDomXCoord(hoverTime) + const cursorOffset = 16 + const legendPosition = dygraph.toDomXCoord(hoverTime) + cursorOffset return makeLegendStyles(graphDiv, this.legendRef, legendPosition) } } From 41a82e2f6905feb838416264d646a9a4a87953ac Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 27 Jun 2018 16:41:43 -0700 Subject: [PATCH 10/59] Remove extra div --- ui/src/shared/components/Dygraph.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/src/shared/components/Dygraph.tsx b/ui/src/shared/components/Dygraph.tsx index d785c227f3..5b5e98e209 100644 --- a/ui/src/shared/components/Dygraph.tsx +++ b/ui/src/shared/components/Dygraph.tsx @@ -268,7 +268,6 @@ class Dygraph extends Component { />
)} -
{staticLegend && ( Date: Wed, 27 Jun 2018 16:44:18 -0700 Subject: [PATCH 11/59] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c43f37105..afbea64315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ 1. [#3671](https://github.com/influxdata/chronograf/pull/3671): Remove Snip functionality in hover legend 1. [#3659](https://github.com/influxdata/chronograf/pull/3659): Upgrade Data Explorer query text field with syntax highlighting and partial multi-line support 1. [#3663](https://github.com/influxdata/chronograf/pull/3663): Truncate message preview in Alert Rules table +1. [#3770](https://github.com/influxdata/chronograf/pull/3770): Improve performance of graph crosshairs ### Bug Fixes From d262281b9b1c3be6fe13c17d03f83d74c5c7162c Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 27 Jun 2018 16:46:06 -0700 Subject: [PATCH 12/59] Prevent lingering crosshair when mouse leaves dygraph --- ui/src/shared/components/Dygraph.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/src/shared/components/Dygraph.tsx b/ui/src/shared/components/Dygraph.tsx index 5b5e98e209..c73b22c798 100644 --- a/ui/src/shared/components/Dygraph.tsx +++ b/ui/src/shared/components/Dygraph.tsx @@ -244,7 +244,11 @@ class Dygraph extends Component { const {staticLegend, cellID} = this.props return ( -
+
{this.dygraph && (
{this.areAnnotationsVisible && ( From 1ea46426723e7bdcdf7bf642d8899fbb9c088555 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 26 Jun 2018 13:10:38 -0700 Subject: [PATCH 13/59] Return query duration from /query endpoint --- influx/templates.go | 1 + influx/templates_test.go | 6 ++++++ server/queries.go | 10 ++++++++++ server/queries_test.go | 6 +++--- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/influx/templates.go b/influx/templates.go index 86168f7727..3bdefb5036 100644 --- a/influx/templates.go +++ b/influx/templates.go @@ -11,6 +11,7 @@ import ( ) // SortTemplates the templates by size, then type, then value. +// :interval: needs to be the last template replacement func SortTemplates(ts []chronograf.TemplateVar) []chronograf.TemplateVar { sort.Slice(ts, func(i, j int) bool { if ts[i].Var == ":interval:" { diff --git a/influx/templates_test.go b/influx/templates_test.go index 3da6ef421f..c92c7c74c0 100644 --- a/influx/templates_test.go +++ b/influx/templates_test.go @@ -448,6 +448,12 @@ func Test_RenderTemplate(t *testing.T) { resolution: 333, want: "SELECT mean(usage_idle) FROM cpu WHERE time > '2017-07-24T15:33:42.994Z' and time < '2017-07-24T15:33:42.994Z' GROUP BY time(1ms)", }, + { + name: "subqueries render :interval:", + query: `SELECT last(sum) FROM (SELECT sum(rate) FROM (SELECT non_negative_derivative(max("counter"),1s) AS "rate" FROM "kube-infra"."monthly"."http_api_requests_total" WHERE path != '/metrics' and time > now() - 1d GROUP BY time(:interval:),* FILL(null)) GROUP BY time(:interval:))`, + resolution: 333, + want: `SELECT last(sum) FROM (SELECT sum(rate) FROM (SELECT non_negative_derivative(max("counter"),1s) AS "rate" FROM "kube-infra"."monthly"."http_api_requests_total" WHERE path != '/metrics' and time > now() - 1d GROUP BY time(25909s),* FILL(null)) GROUP BY time(259s))`, + }, { name: "query should be returned if there are no template variables", query: "SHOW DATABASES", diff --git a/server/queries.go b/server/queries.go index c48d5ef49e..936333e77c 100644 --- a/server/queries.go +++ b/server/queries.go @@ -29,6 +29,7 @@ type QueriesRequest struct { // QueryResponse is the return result of a QueryRequest including // the raw query, the templated query, the queryConfig and the queryAST type QueryResponse struct { + Duration int64 `json:"durationMs"` ID string `json:"id"` Query string `json:"query"` QueryConfig chronograf.QueryConfig `json:"queryConfig"` @@ -90,6 +91,15 @@ func (s *Service) Queries(w http.ResponseWriter, r *http.Request) { qr.QueryAST = stmt } + if dur, err := influx.ParseTime(query, time.Now()); err == nil { + ms := dur.Nanoseconds() / int64(time.Millisecond) + if ms == 0 { + ms = 1 + } + + qr.Duration = ms + } + if len(req.TemplateVars) > 0 { qr.TemplateVars = req.TemplateVars qr.QueryConfig.RawText = &qr.Query diff --git a/server/queries_test.go b/server/queries_test.go index 754220403e..86aa4358ac 100644 --- a/server/queries_test.go +++ b/server/queries_test.go @@ -60,7 +60,7 @@ func TestService_Queries(t *testing.T) { "id": "82b60d37-251e-4afe-ac93-ca20a3642b11" } ]}`))), - want: `{"queries":[{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","query":"SELECT \"pingReq\" FROM db.\"monitor\".\"httpd\" WHERE time \u003e now() - 1m","queryConfig":{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","database":"db","measurement":"httpd","retentionPolicy":"monitor","fields":[{"value":"pingReq","type":"field","alias":""}],"tags":{},"groupBy":{"time":"","tags":[]},"areTagsAccepted":false,"rawText":null,"range":{"upper":"","lower":"now() - 1m"},"shifts":[]},"queryAST":{"condition":{"expr":"binary","op":"\u003e","lhs":{"expr":"reference","val":"time"},"rhs":{"expr":"binary","op":"-","lhs":{"expr":"call","name":"now"},"rhs":{"expr":"literal","val":"1m","type":"duration"}}},"fields":[{"column":{"expr":"reference","val":"pingReq"}}],"sources":[{"database":"db","retentionPolicy":"monitor","name":"httpd","type":"measurement"}]}}]} + want: `{"queries":[{"durationMs":59999,"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","query":"SELECT \"pingReq\" FROM db.\"monitor\".\"httpd\" WHERE time \u003e now() - 1m","queryConfig":{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","database":"db","measurement":"httpd","retentionPolicy":"monitor","fields":[{"value":"pingReq","type":"field","alias":""}],"tags":{},"groupBy":{"time":"","tags":[]},"areTagsAccepted":false,"rawText":null,"range":{"upper":"","lower":"now() - 1m"},"shifts":[]},"queryAST":{"condition":{"expr":"binary","op":"\u003e","lhs":{"expr":"reference","val":"time"},"rhs":{"expr":"binary","op":"-","lhs":{"expr":"call","name":"now"},"rhs":{"expr":"literal","val":"1m","type":"duration"}}},"fields":[{"column":{"expr":"reference","val":"pingReq"}}],"sources":[{"database":"db","retentionPolicy":"monitor","name":"httpd","type":"measurement"}]}}]} `, }, { @@ -81,7 +81,7 @@ func TestService_Queries(t *testing.T) { "id": "82b60d37-251e-4afe-ac93-ca20a3642b11" } ]}`))), - want: `{"queries":[{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","query":"SHOW DATABASES","queryConfig":{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","database":"","measurement":"","retentionPolicy":"","fields":[],"tags":{},"groupBy":{"time":"","tags":[]},"areTagsAccepted":false,"rawText":"SHOW DATABASES","range":null,"shifts":[]}}]} + want: `{"queries":[{"durationMs":0,"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","query":"SHOW DATABASES","queryConfig":{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","database":"","measurement":"","retentionPolicy":"","fields":[],"tags":{},"groupBy":{"time":"","tags":[]},"areTagsAccepted":false,"rawText":"SHOW DATABASES","range":null,"shifts":[]}}]} `, }, { @@ -162,7 +162,7 @@ func TestService_Queries(t *testing.T) { } ] }`))), - want: `{"queries":[{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","query":"SELECT \"pingReq\" FROM :dbs:.\"monitor\".\"httpd\" WHERE time \u003e :dashboardTime: AND time \u003c :upperDashboardTime: GROUP BY time(:interval:)","queryConfig":{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","database":"","measurement":"","retentionPolicy":"","fields":[],"tags":{},"groupBy":{"time":"","tags":[]},"areTagsAccepted":false,"rawText":"SELECT \"pingReq\" FROM :dbs:.\"monitor\".\"httpd\" WHERE time \u003e :dashboardTime: AND time \u003c :upperDashboardTime: GROUP BY time(:interval:)","range":null,"shifts":[]},"queryTemplated":"SELECT \"pingReq\" FROM \"_internal\".\"monitor\".\"httpd\" WHERE time \u003e now() - 15m AND time \u003c now() GROUP BY time(2s)","tempVars":[{"tempVar":":upperDashboardTime:","values":[{"value":"now()","type":"constant","selected":true}]},{"tempVar":":dashboardTime:","values":[{"value":"now() - 15m","type":"constant","selected":true}]},{"tempVar":":dbs:","values":[{"value":"_internal","type":"database","selected":true}]},{"tempVar":":interval:","values":[{"value":"333","type":"points","selected":false}]}]}]} + want: `{"queries":[{"durationMs":899999,"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","query":"SELECT \"pingReq\" FROM :dbs:.\"monitor\".\"httpd\" WHERE time \u003e :dashboardTime: AND time \u003c :upperDashboardTime: GROUP BY time(:interval:)","queryConfig":{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","database":"","measurement":"","retentionPolicy":"","fields":[],"tags":{},"groupBy":{"time":"","tags":[]},"areTagsAccepted":false,"rawText":"SELECT \"pingReq\" FROM :dbs:.\"monitor\".\"httpd\" WHERE time \u003e :dashboardTime: AND time \u003c :upperDashboardTime: GROUP BY time(:interval:)","range":null,"shifts":[]},"queryTemplated":"SELECT \"pingReq\" FROM \"_internal\".\"monitor\".\"httpd\" WHERE time \u003e now() - 15m AND time \u003c now() GROUP BY time(2s)","tempVars":[{"tempVar":":upperDashboardTime:","values":[{"value":"now()","type":"constant","selected":true}]},{"tempVar":":dashboardTime:","values":[{"value":"now() - 15m","type":"constant","selected":true}]},{"tempVar":":dbs:","values":[{"value":"_internal","type":"database","selected":true}]},{"tempVar":":interval:","values":[{"value":"333","type":"points","selected":false}]}]}]} `, }, } From 3c23b8cd9a15e071b7e8b9cd195ec85ede1fd3bf Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 26 Jun 2018 14:01:26 -0700 Subject: [PATCH 14/59] WIP template variable replacement frontend --- ui/src/tempVars/utils/replace.ts | 52 ++++++++++ ui/src/types/tempVars.ts | 1 + ui/test/tempVars/utils/replace.test.ts | 125 +++++++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 ui/src/tempVars/utils/replace.ts create mode 100644 ui/test/tempVars/utils/replace.test.ts diff --git a/ui/src/tempVars/utils/replace.ts b/ui/src/tempVars/utils/replace.ts new file mode 100644 index 0000000000..962f9e3b4c --- /dev/null +++ b/ui/src/tempVars/utils/replace.ts @@ -0,0 +1,52 @@ +import {Template, TemplateValueType, TemplateValue} from 'src/types/tempVars' + +const templateReplace = (q: string, tempVars: Template[]) => { + const query = tempVars.reduce((acc, template) => { + const qu = renderTemplate(acc, template) + return qu + }, q) + + return query +} + +const renderTemplate = (query: string, template: Template): string => { + if (!template.values.length) { + return query + } + + if (query && !query.includes(template.tempVar)) { + return query + } + + const templateValue: TemplateValue = template.values.find(v => v.selected) + + if (!templateValue) { + return query + } + + const {tempVar} = template + const {value, type} = templateValue + + switch (type) { + case TemplateValueType.TagKey: + case TemplateValueType.FieldKey: + case TemplateValueType.Measurement: + case TemplateValueType.Database: + return replaceAll(query, tempVar, `"${value}"`) + case TemplateValueType.TagValue: + case TemplateValueType.TimeStamp: + return replaceAll(query, tempVar, `'${value}'`) + case TemplateValueType.CSV: + case TemplateValueType.Constant: + case TemplateValueType.MetaQuery: + return replaceAll(query, tempVar, value) + default: + return query + } +} + +const replaceAll = (query: string, search: string, replacement: string) => { + return query.split(search).join(replacement) +} + +export default templateReplace diff --git a/ui/src/types/tempVars.ts b/ui/src/types/tempVars.ts index cc17663c4a..1d524aa101 100644 --- a/ui/src/types/tempVars.ts +++ b/ui/src/types/tempVars.ts @@ -10,6 +10,7 @@ export enum TemplateValueType { Points = 'points', Constant = 'constant', MetaQuery = 'influxql', + TimeStamp = 'timeStamp', } export interface TemplateValue { diff --git a/ui/test/tempVars/utils/replace.test.ts b/ui/test/tempVars/utils/replace.test.ts new file mode 100644 index 0000000000..a5189e6847 --- /dev/null +++ b/ui/test/tempVars/utils/replace.test.ts @@ -0,0 +1,125 @@ +import templateReplace from 'src/tempVars/utils/replace' +import {TemplateValueType} from 'src/types/tempVars' + +describe('templates.utils.replace', () => { + describe('template replacement', () => { + it('can replace select with parameters', () => { + const query = + ':method: field1, :field: FROM :measurement: WHERE temperature > :temperature:' + + const vars = [ + { + tempVar: ':temperature:', + values: [{type: TemplateValueType.CSV, value: '10', selected: true}], + }, + { + tempVar: ':field:', + values: [ + {type: TemplateValueType.FieldKey, value: 'field2', selected: true}, + ], + }, + { + tempVar: ':method:', + values: [ + {type: TemplateValueType.CSV, value: 'SELECT', selected: true}, + ], + }, + { + tempVar: ':measurement:', + values: [ + {type: TemplateValueType.CSV, value: `"cpu"`, selected: true}, + ], + }, + ] + + const expected = `SELECT field1, "field2" FROM "cpu" WHERE temperature > 10` + + const actual = templateReplace(query, vars) + expect(actual).toBe(expected) + }) + }) + + it('can replace all in a select with paramaters and aggregates', () => { + const vars = [ + { + tempVar: ':value:', + values: [ + { + type: TemplateValueType.TagValue, + value: 'howdy.com', + selected: true, + }, + ], + }, + { + tempVar: ':tag:', + values: [ + {type: TemplateValueType.TagKey, value: 'host', selected: true}, + ], + }, + { + tempVar: ':field:', + values: [ + {type: TemplateValueType.FieldKey, value: 'field', selected: true}, + ], + }, + ] + + const query = `SELECT mean(:field:) FROM "cpu" WHERE :tag: = :value: GROUP BY :tag:` + const expected = `SELECT mean("field") FROM "cpu" WHERE "host" = 'howdy.com' GROUP BY "host"` + const actual = templateReplace(query, vars) + + expect(actual).toBe(expected) + }) + + describe('sad path', () => { + describe('with no templates', () => { + it('does not do a replacement', () => { + const query = `SELECT :field: FROM "cpu"` + const expected = query + const actual = templateReplace(query, []) + + expect(actual).toBe(expected) + }) + }) + + describe('with no template values', () => { + it('does not do a replacement', () => { + const vars = [ + { + tempVar: ':field:', + values: [], + }, + ] + const query = `SELECT :field: FROM "cpu"` + const expected = query + const actual = templateReplace(query, []) + + expect(actual).toBe(expected) + }) + }) + + describe('with an unknown template type', () => { + it('does not do a replacement', () => { + const vars = [ + { + tempVar: ':field:', + values: [ + { + type: 'howdy', + value: 'field', + selected: true, + }, + ], + }, + ] + + const query = `SELECT :field: FROM "cpu"` + const expected = query + const actual = templateReplace(query, vars) + + expect(actual).toBe(expected) + }) + }) + }) +}) From a41a2646ca71ff17f3581647785e0114daa4b0cb Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Tue, 26 Jun 2018 16:52:54 -0700 Subject: [PATCH 15/59] Calc auto group by on the frontend --- ui/src/tempVars/utils/replace.ts | 27 +++- ui/test/resources.ts | 9 ++ ui/test/tempVars/utils/replace.test.ts | 188 +++++++++++++++++-------- 3 files changed, 156 insertions(+), 68 deletions(-) diff --git a/ui/src/tempVars/utils/replace.ts b/ui/src/tempVars/utils/replace.ts index 962f9e3b4c..54e15734b5 100644 --- a/ui/src/tempVars/utils/replace.ts +++ b/ui/src/tempVars/utils/replace.ts @@ -1,12 +1,27 @@ import {Template, TemplateValueType, TemplateValue} from 'src/types/tempVars' +import {TEMP_VAR_INTERVAL} from 'src/shared/constants' -const templateReplace = (q: string, tempVars: Template[]) => { - const query = tempVars.reduce((acc, template) => { - const qu = renderTemplate(acc, template) - return qu - }, q) +export const intervalReplace = ( + query: string, + pixels: number, + durationMs: number +) => { + if (!query.includes(TEMP_VAR_INTERVAL)) { + return query + } - return query + // duration / width of visualization in pixels + const msPerPixel = Math.floor(durationMs / pixels) + + return replaceAll(query, TEMP_VAR_INTERVAL, `${msPerPixel}ms`) +} + +const templateReplace = (query: string, tempVars: Template[]) => { + const replacedQuery = tempVars.reduce((acc, template) => { + return renderTemplate(acc, template) + }, query) + + return replacedQuery } const renderTemplate = (query: string, template: Template): string => { diff --git a/ui/test/resources.ts b/ui/test/resources.ts index ed7aa7baa9..cc90dd5fc9 100644 --- a/ui/test/resources.ts +++ b/ui/test/resources.ts @@ -608,6 +608,15 @@ export const template: Template = { ], } +export const emptyTemplate: Template = { + id: '1', + type: TemplateType.CSV, + label: '', + tempVar: '', + query: {}, + values: [], +} + export const dashboard: Dashboard = { id: 1, cells: [], diff --git a/ui/test/tempVars/utils/replace.test.ts b/ui/test/tempVars/utils/replace.test.ts index a5189e6847..66527d57fa 100644 --- a/ui/test/tempVars/utils/replace.test.ts +++ b/ui/test/tempVars/utils/replace.test.ts @@ -1,47 +1,49 @@ -import templateReplace from 'src/tempVars/utils/replace' +import templateReplace, {intervalReplace} from 'src/tempVars/utils/replace' import {TemplateValueType} from 'src/types/tempVars' +import {emptyTemplate} from 'test/resources' describe('templates.utils.replace', () => { - describe('template replacement', () => { - it('can replace select with parameters', () => { - const query = - ':method: field1, :field: FROM :measurement: WHERE temperature > :temperature:' + it('can replace select with parameters', () => { + const query = + ':method: field1, :field: FROM :measurement: WHERE temperature > :temperature:' - const vars = [ - { - tempVar: ':temperature:', - values: [{type: TemplateValueType.CSV, value: '10', selected: true}], - }, - { - tempVar: ':field:', - values: [ - {type: TemplateValueType.FieldKey, value: 'field2', selected: true}, - ], - }, - { - tempVar: ':method:', - values: [ - {type: TemplateValueType.CSV, value: 'SELECT', selected: true}, - ], - }, - { - tempVar: ':measurement:', - values: [ - {type: TemplateValueType.CSV, value: `"cpu"`, selected: true}, - ], - }, - ] + const vars = [ + { + ...emptyTemplate, + tempVar: ':temperature:', + values: [{type: TemplateValueType.CSV, value: '10', selected: true}], + }, + { + ...emptyTemplate, + tempVar: ':field:', + values: [ + {type: TemplateValueType.FieldKey, value: 'field2', selected: true}, + ], + }, + { + ...emptyTemplate, + tempVar: ':method:', + values: [ + {type: TemplateValueType.CSV, value: 'SELECT', selected: true}, + ], + }, + { + ...emptyTemplate, + tempVar: ':measurement:', + values: [{type: TemplateValueType.CSV, value: `"cpu"`, selected: true}], + }, + ] - const expected = `SELECT field1, "field2" FROM "cpu" WHERE temperature > 10` + const expected = `SELECT field1, "field2" FROM "cpu" WHERE temperature > 10` - const actual = templateReplace(query, vars) - expect(actual).toBe(expected) - }) + const actual = templateReplace(query, vars) + expect(actual).toBe(expected) }) it('can replace all in a select with paramaters and aggregates', () => { const vars = [ { + ...emptyTemplate, tempVar: ':value:', values: [ { @@ -52,12 +54,14 @@ describe('templates.utils.replace', () => { ], }, { + ...emptyTemplate, tempVar: ':tag:', values: [ {type: TemplateValueType.TagKey, value: 'host', selected: true}, ], }, { + ...emptyTemplate, tempVar: ':field:', values: [ {type: TemplateValueType.FieldKey, value: 'field', selected: true}, @@ -72,51 +76,111 @@ describe('templates.utils.replace', () => { expect(actual).toBe(expected) }) - describe('sad path', () => { - describe('with no templates', () => { - it('does not do a replacement', () => { - const query = `SELECT :field: FROM "cpu"` - const expected = query - const actual = templateReplace(query, []) + describe('with no templates', () => { + it('does not do a replacement', () => { + const query = `SELECT :field: FROM "cpu"` + const expected = query + const actual = templateReplace(query, []) - expect(actual).toBe(expected) - }) + expect(actual).toBe(expected) + }) + }) + + describe('with no template values', () => { + it('does not do a replacement', () => { + const vars = [ + { + ...emptyTemplate, + tempVar: ':field:', + values: [], + }, + ] + const query = `SELECT :field: FROM "cpu"` + const expected = query + const actual = templateReplace(query, vars) + + expect(actual).toBe(expected) + }) + }) + + describe('intervalReplace', () => { + it('can replace :interval:', () => { + const query = `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(:interval:)` + const expected = `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(46702702ms)` + const pixels = 333 + const durationMs = 15551999999 + const actual = intervalReplace(query, pixels, durationMs) + + expect(actual).toBe(expected) }) - describe('with no template values', () => { - it('does not do a replacement', () => { - const vars = [ - { - tempVar: ':field:', - values: [], - }, - ] - const query = `SELECT :field: FROM "cpu"` - const expected = query - const actual = templateReplace(query, []) + it('can replace multiple intervals', () => { + const query = `SELECT NON_NEGATIVE_DERIVATIVE(mean(usage_idle), :interval:) from "cpu" where time > now() - 4320h group by time(:interval:)` + const expected = `SELECT NON_NEGATIVE_DERIVATIVE(mean(usage_idle), 46702702ms) from "cpu" where time > now() - 4320h group by time(46702702ms)` - expect(actual).toBe(expected) - }) + const pixels = 333 + const durationMs = 15551999999 + const actual = intervalReplace(query, pixels, durationMs) + + expect(actual).toBe(expected) }) - describe('with an unknown template type', () => { - it('does not do a replacement', () => { + describe('when used with other template variables', () => { + it('can work with :dashboardTime:', () => { const vars = [ { - tempVar: ':field:', + ...emptyTemplate, + tempVar: ':dashboardTime:', values: [ { - type: 'howdy', - value: 'field', + type: TemplateValueType.Constant, + value: 'now() - 24h', selected: true, }, ], }, ] - const query = `SELECT :field: FROM "cpu"` - const expected = query - const actual = templateReplace(query, vars) + const pixels = 333 + const durationMs = 86399999 + const query = `SELECT mean(usage_idle) from "cpu" WHERE time > :dashboardTime: group by time(:interval:)` + let actual = templateReplace(query, vars) + actual = intervalReplace(actual, pixels, durationMs) + const expected = `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 24h group by time(259459ms)` + + expect(actual).toBe(expected) + }) + + it('can handle a failing condition', () => { + const vars = [ + { + ...emptyTemplate, + tempVar: ':dashboardTime:', + values: [ + { + type: TemplateValueType.Constant, + value: 'now() - 1h', + selected: true, + }, + ], + }, + ] + + const pixels = 38 + const durationMs = 3599999 + const query = `SELECT mean(usage_idle) from "cpu" WHERE time > :dashboardTime: group by time(:interval:)` + let actual = templateReplace(query, vars) + actual = intervalReplace(actual, pixels, durationMs) + const expected = `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 1h group by time(94736ms)` + + expect(actual).toBe(expected) + }) + }) + + describe('with no :interval: present', () => { + it('returns the query', () => { + const expected = `SELECT mean(usage_idle) FROM "cpu" WHERE time > :dashboardTime: GROUP BY time(20ms)` + const actual = intervalReplace(expected, 10, 20000) expect(actual).toBe(expected) }) From f47981f362e6772058432c697e8d035b981ea145 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 27 Jun 2018 11:32:39 -0700 Subject: [PATCH 16/59] Move replate replacement functions to the frontend --- ui/src/tempVars/utils/replace.ts | 38 ++++++++++++++++++++++-- ui/test/tempVars/utils/replace.test.ts | 41 +++++++++++++++++++++++--- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/ui/src/tempVars/utils/replace.ts b/ui/src/tempVars/utils/replace.ts index 54e15734b5..dbdf290f5d 100644 --- a/ui/src/tempVars/utils/replace.ts +++ b/ui/src/tempVars/utils/replace.ts @@ -42,24 +42,56 @@ const renderTemplate = (query: string, template: Template): string => { const {tempVar} = template const {value, type} = templateValue + let q = '' + + // First replace all template variable types in regular expressions. Values should appear unquoted. switch (type) { case TemplateValueType.TagKey: case TemplateValueType.FieldKey: case TemplateValueType.Measurement: case TemplateValueType.Database: - return replaceAll(query, tempVar, `"${value}"`) case TemplateValueType.TagValue: case TemplateValueType.TimeStamp: - return replaceAll(query, tempVar, `'${value}'`) + q = replaceAllRegex(query, tempVar, value) + break + default: + q = query + } + + // Then render template variables not in regular expressions + switch (type) { + case TemplateValueType.TagKey: + case TemplateValueType.FieldKey: + case TemplateValueType.Measurement: + case TemplateValueType.Database: + return replaceAll(q, tempVar, `"${value}"`) + case TemplateValueType.TagValue: + case TemplateValueType.TimeStamp: + return replaceAll(q, tempVar, `'${value}'`) case TemplateValueType.CSV: case TemplateValueType.Constant: case TemplateValueType.MetaQuery: - return replaceAll(query, tempVar, value) + return replaceAll(q, tempVar, value) default: return query } } +const replaceAllRegex = ( + query: string, + search: string, + replacement: string +) => { + const matches = query.match(/\/([^\/]*)\//gm) + const isReplaceable = !!matches && matches.some(m => m.includes(search)) + + if (!isReplaceable) { + return query + } + + return replaceAll(query, search, replacement) +} + const replaceAll = (query: string, search: string, replacement: string) => { return query.split(search).join(replacement) } diff --git a/ui/test/tempVars/utils/replace.test.ts b/ui/test/tempVars/utils/replace.test.ts index 66527d57fa..7d5465ff03 100644 --- a/ui/test/tempVars/utils/replace.test.ts +++ b/ui/test/tempVars/utils/replace.test.ts @@ -4,9 +4,6 @@ import {emptyTemplate} from 'test/resources' describe('templates.utils.replace', () => { it('can replace select with parameters', () => { - const query = - ':method: field1, :field: FROM :measurement: WHERE temperature > :temperature:' - const vars = [ { ...emptyTemplate, @@ -33,7 +30,8 @@ describe('templates.utils.replace', () => { values: [{type: TemplateValueType.CSV, value: `"cpu"`, selected: true}], }, ] - + const query = + ':method: field1, :field: FROM :measurement: WHERE temperature > :temperature:' const expected = `SELECT field1, "field2" FROM "cpu" WHERE temperature > 10` const actual = templateReplace(query, vars) @@ -76,6 +74,41 @@ describe('templates.utils.replace', () => { expect(actual).toBe(expected) }) + describe('queries with a regex', () => { + it('replaces properly', () => { + const vars = [ + { + ...emptyTemplate, + tempVar: ':host:', + values: [ + { + type: TemplateValueType.TagValue, + value: 'my-host.local', + selected: true, + }, + ], + }, + { + ...emptyTemplate, + tempVar: ':dashboardTime:', + values: [ + { + value: 'now() - 1h', + type: TemplateValueType.Constant, + selected: true, + }, + ], + }, + ] + + const query = `SELECT "usage_active" FROM "cpu" WHERE host =~ /^:host:$/ AND time > :dashboardTime: FILL(null)` + const expected = `SELECT "usage_active" FROM "cpu" WHERE host =~ /^my-host.local$/ AND time > now() - 1h FILL(null)` + const actual = templateReplace(query, vars) + + expect(actual).toBe(expected) + }) + }) + describe('with no templates', () => { it('does not do a replacement', () => { const query = `SELECT :field: FROM "cpu"` From 5f0a0ae24d04130a8b41afec16c9d85c3915e8b5 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 27 Jun 2018 12:09:40 -0700 Subject: [PATCH 17/59] Refine replacement of Regex --- ui/src/tempVars/utils/replace.ts | 13 ++++++++++--- ui/test/tempVars/utils/replace.test.ts | 15 +++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ui/src/tempVars/utils/replace.ts b/ui/src/tempVars/utils/replace.ts index dbdf290f5d..334f1bb3aa 100644 --- a/ui/src/tempVars/utils/replace.ts +++ b/ui/src/tempVars/utils/replace.ts @@ -82,14 +82,21 @@ const replaceAllRegex = ( search: string, replacement: string ) => { + // check for presence of anythine between two forward slashes /[your stuff here]/ const matches = query.match(/\/([^\/]*)\//gm) - const isReplaceable = !!matches && matches.some(m => m.includes(search)) - if (!isReplaceable) { + if (!matches) { return query } - return replaceAll(query, search, replacement) + return matches.reduce((acc, m) => { + if (m.includes(search)) { + const replaced = m.replace(search, replacement) + return acc.split(m).join(replaced) + } + + return acc + }, query) } const replaceAll = (query: string, search: string, replacement: string) => { diff --git a/ui/test/tempVars/utils/replace.test.ts b/ui/test/tempVars/utils/replace.test.ts index 7d5465ff03..21516b2355 100644 --- a/ui/test/tempVars/utils/replace.test.ts +++ b/ui/test/tempVars/utils/replace.test.ts @@ -88,6 +88,17 @@ describe('templates.utils.replace', () => { }, ], }, + { + ...emptyTemplate, + tempVar: ':region:', + values: [ + { + type: TemplateValueType.TagValue, + value: 'north', + selected: true, + }, + ], + }, { ...emptyTemplate, tempVar: ':dashboardTime:', @@ -101,8 +112,8 @@ describe('templates.utils.replace', () => { }, ] - const query = `SELECT "usage_active" FROM "cpu" WHERE host =~ /^:host:$/ AND time > :dashboardTime: FILL(null)` - const expected = `SELECT "usage_active" FROM "cpu" WHERE host =~ /^my-host.local$/ AND time > now() - 1h FILL(null)` + const query = `SELECT "usage_active" FROM "cpu" WHERE host =~ /^:host:$/ AND host = :host: AND region =~ /:region:/ AND time > :dashboardTime: FILL(null)` + const expected = `SELECT "usage_active" FROM "cpu" WHERE host =~ /^my-host.local$/ AND host = 'my-host.local' AND region =~ /north/ AND time > now() - 1h FILL(null)` const actual = templateReplace(query, vars) expect(actual).toBe(expected) From 2eeb8f1d97595da7894fac6f7530b8c79d882d0a Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 27 Jun 2018 12:30:56 -0700 Subject: [PATCH 18/59] Fix go test --- influx/templates_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/influx/templates_test.go b/influx/templates_test.go index c92c7c74c0..d8e537110a 100644 --- a/influx/templates_test.go +++ b/influx/templates_test.go @@ -450,9 +450,9 @@ func Test_RenderTemplate(t *testing.T) { }, { name: "subqueries render :interval:", - query: `SELECT last(sum) FROM (SELECT sum(rate) FROM (SELECT non_negative_derivative(max("counter"),1s) AS "rate" FROM "kube-infra"."monthly"."http_api_requests_total" WHERE path != '/metrics' and time > now() - 1d GROUP BY time(:interval:),* FILL(null)) GROUP BY time(:interval:))`, + query: `SELECT last(sum) FROM (SELECT sum(rate) FROM (SELECT non_negative_derivative(max("counter"),1s) AS "rate" FROM "kube-infra"."monthly"."http_api_requests_total" WHERE path != '/metrics' and time > now() - 1d GROUP BY time(:interval:),* FILL(null)) GROUP BY time(:interval:))`, resolution: 333, - want: `SELECT last(sum) FROM (SELECT sum(rate) FROM (SELECT non_negative_derivative(max("counter"),1s) AS "rate" FROM "kube-infra"."monthly"."http_api_requests_total" WHERE path != '/metrics' and time > now() - 1d GROUP BY time(25909s),* FILL(null)) GROUP BY time(259s))`, + want: `SELECT last(sum) FROM (SELECT sum(rate) FROM (SELECT non_negative_derivative(max("counter"),1s) AS "rate" FROM "kube-infra"."monthly"."http_api_requests_total" WHERE path != '/metrics' and time > now() - 1d GROUP BY time(259s),* FILL(null)) GROUP BY time(259s))`, }, { name: "query should be returned if there are no template variables", From 2e78cb0944d3b1de69764013f1fdda7cce7c25f8 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 27 Jun 2018 14:48:06 -0700 Subject: [PATCH 19/59] First pass at tempVar frontent plumbing --- .../dashboards/components/Visualization.tsx | 1 + ui/src/data_explorer/components/Table.tsx | 2 - ui/src/data_explorer/components/VisView.tsx | 26 +++--- ui/src/kapacitor/components/RuleGraph.js | 1 + ui/src/shared/actions/timeSeries.ts | 5 +- ui/src/shared/apis/index.ts | 31 ++++++- ui/src/shared/apis/query.ts | 81 +++++++++++-------- ui/src/shared/components/AutoRefresh.tsx | 24 ++++-- ui/src/shared/components/Layout.js | 1 + ui/src/shared/components/RefreshingGraph.js | 6 ++ ui/src/tempVars/utils/replace.ts | 10 ++- .../shared/components/AutoRefresh.test.tsx | 2 + ui/test/tempVars/utils/replace.test.ts | 14 ++-- 13 files changed, 134 insertions(+), 70 deletions(-) diff --git a/ui/src/dashboards/components/Visualization.tsx b/ui/src/dashboards/components/Visualization.tsx index 98b9d440ce..52c1ca0fa4 100644 --- a/ui/src/dashboards/components/Visualization.tsx +++ b/ui/src/dashboards/components/Visualization.tsx @@ -71,6 +71,7 @@ const DashVisualization: SFC = ({ {(source: Source) => ( { const {results} = await fetchTimeSeriesAsync({ source: this.source, query, - tempVars: TEMPLATES, }) this.setState({ diff --git a/ui/src/data_explorer/components/VisView.tsx b/ui/src/data_explorer/components/VisView.tsx index c311128611..6530e1e55b 100644 --- a/ui/src/data_explorer/components/VisView.tsx +++ b/ui/src/data_explorer/components/VisView.tsx @@ -3,8 +3,9 @@ import React, {SFC} from 'react' import Table from './Table' import RefreshingGraph from 'src/shared/components/RefreshingGraph' import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes' +import {SourceContext} from 'src/CheckSources' -import {Query, Template} from 'src/types' +import {Source, Query, Template} from 'src/types' interface Props { view: string @@ -38,15 +39,20 @@ const DataExplorerVisView: SFC = ({ } return ( - + + {(source: Source) => ( + + )} + ) } diff --git a/ui/src/kapacitor/components/RuleGraph.js b/ui/src/kapacitor/components/RuleGraph.js index 60e238c148..7dbe4f8996 100644 --- a/ui/src/kapacitor/components/RuleGraph.js +++ b/ui/src/kapacitor/components/RuleGraph.js @@ -44,6 +44,7 @@ const RuleGraph = ({ />
=> { handleLoading(query, editQueryStatus) @@ -91,7 +89,6 @@ export const fetchTimeSeriesAsync = async ( db, rp, query: query.text, - tempVars, resolution, }) return handleSuccess(data, query, editQueryStatus) diff --git a/ui/src/shared/apis/index.ts b/ui/src/shared/apis/index.ts index eb51aefeb3..24e9298ae6 100644 --- a/ui/src/shared/apis/index.ts +++ b/ui/src/shared/apis/index.ts @@ -1,6 +1,6 @@ import AJAX from 'src/utils/ajax' import {AlertTypes} from 'src/kapacitor/constants' -import {Kapacitor, Source, Service, NewService} from 'src/types' +import {Kapacitor, Source, Service, NewService, QueryConfig} from 'src/types' export function getSources() { return AJAX({ @@ -319,12 +319,37 @@ export function kapacitorProxy(kapacitor, method, path, body?) { }) } -export const getQueryConfigAndStatus = (url, queries, tempVars = []) => - AJAX({ +export const getQueryConfigAndStatus = (url, queries, tempVars = []) => { + return AJAX({ url, method: 'POST', data: {queries, tempVars}, }) +} + +interface AnalyzeQueriesResponse { + query: string + duration: string + queryConfig?: QueryConfig +} + +export const analyzeQueries = async ( + url: string, + queries: Array<{query: string}> +): Promise => { + try { + const {data} = await AJAX({ + url, + method: 'POST', + data: {queries}, + }) + + return data.queries + } catch (error) { + console.error(error) + throw error + } +} export const getServices = async (url: string): Promise => { try { diff --git a/ui/src/shared/apis/query.ts b/ui/src/shared/apis/query.ts index 6ef59a4213..8420dfa2f7 100644 --- a/ui/src/shared/apis/query.ts +++ b/ui/src/shared/apis/query.ts @@ -1,13 +1,13 @@ import _ from 'lodash' +import {getDeep} from 'src/utils/wrappers' import {fetchTimeSeriesAsync} from 'src/shared/actions/timeSeries' -import {removeUnselectedTemplateValues} from 'src/tempVars/constants' - -import {intervalValuesPoints} from 'src/shared/constants' +import {analyzeQueries} from 'src/shared/apis' +import replaceTemplates, {replaceInterval} from 'src/tempVars/utils/replace' +import {Source} from 'src/types' import {Template} from 'src/types' interface Query { - host: string | string[] text: string database: string db: string @@ -15,49 +15,60 @@ interface Query { id: string } -const parseSource = source => { - if (Array.isArray(source)) { - return _.get(source, '0', '') - } - - return source -} - export const fetchTimeSeries = async ( + source: Source, queries: Query[], resolution: number, templates: Template[], editQueryStatus: () => any ) => { - const timeSeriesPromises = queries.map(query => { - const {host, database, rp} = query - // the key `database` was used upstream in HostPage.js, and since as of this writing - // the codebase has not been fully converted to TypeScript, it's not clear where else - // it may be used, but this slight modification is intended to allow for the use of - // `database` while moving over to `db` for consistency over time + const timeSeriesPromises = queries.map(async query => { + const {database, rp} = query const db = _.get(query, 'db', database) - const templatesWithIntervalVals = templates.map(temp => { - if (temp.tempVar === ':interval:') { - if (resolution) { - const values = temp.values.map(v => ({ - ...v, - value: `${_.toInteger(Number(resolution) / 3)}`, - })) - return {...temp, values} - } + try { + const text = await replace(query.text, source, templates, resolution) - return {...temp, values: intervalValuesPoints} + const payload = { + source: source.links.proxy, + db, + rp, + query: {...query, text}, + resolution, } - return temp - }) - const tempVars = removeUnselectedTemplateValues(templatesWithIntervalVals) - - const source = parseSource(host) - const payload = {source, db, rp, query, tempVars, resolution} - return fetchTimeSeriesAsync(payload, editQueryStatus) + return fetchTimeSeriesAsync(payload, editQueryStatus) + } catch (error) { + console.error(error) + throw error + } }) return Promise.all(timeSeriesPromises) } + +const replace = async ( + query: string, + source: Source, + templates: Template[], + resolution: number +): Promise => { + try { + query = replaceTemplates(query, templates) + const durationMs = await duration(query, source) + return replaceInterval(query, Math.floor(resolution / 3), durationMs) + } catch (error) { + console.error(error) + throw error + } +} + +const duration = async (query: string, source: Source): Promise => { + try { + const analysis = await analyzeQueries(source.links.queries, [{query}]) + return getDeep(analysis, '0.durationMs', 1000) + } catch (error) { + console.error(error) + throw error + } +} diff --git a/ui/src/shared/components/AutoRefresh.tsx b/ui/src/shared/components/AutoRefresh.tsx index 0f639bc4f7..3279411d6c 100644 --- a/ui/src/shared/components/AutoRefresh.tsx +++ b/ui/src/shared/components/AutoRefresh.tsx @@ -4,7 +4,7 @@ import _ from 'lodash' import {fetchTimeSeries} from 'src/shared/apis/query' import {DEFAULT_TIME_SERIES} from 'src/shared/constants/series' import {TimeSeriesServerResponse, TimeSeriesResponse} from 'src/types/series' -import {Template} from 'src/types' +import {Template, Source} from 'src/types' interface Axes { bounds: { @@ -23,15 +23,16 @@ interface Query { } export interface Props { - type: string - autoRefresh: number - inView: boolean - templates: Template[] - queries: Query[] + source: Source axes: Axes + type: string + inView: boolean + queries: Query[] + autoRefresh: number + templates: Template[] editQueryStatus: () => void - grabDataForDownload: (timeSeries: TimeSeriesServerResponse[]) => void onSetResolution?: (resolution: number) => void + grabDataForDownload: (timeSeries: TimeSeriesServerResponse[]) => void } interface State { @@ -80,7 +81,13 @@ const AutoRefresh = ( } public executeQueries = async () => { - const {editQueryStatus, grabDataForDownload, inView, queries} = this.props + const { + source, + editQueryStatus, + grabDataForDownload, + inView, + queries, + } = this.props const {resolution} = this.state if (!inView) { @@ -97,6 +104,7 @@ const AutoRefresh = ( try { const timeSeries = await fetchTimeSeries( + source, queries, resolution, templates, diff --git a/ui/src/shared/components/Layout.js b/ui/src/shared/components/Layout.js index 92d97d21c4..4b69d8a782 100644 --- a/ui/src/shared/components/Layout.js +++ b/ui/src/shared/components/Layout.js @@ -124,6 +124,7 @@ const Layout = ( host )} onSetResolution={onSetResolution} + source={getSource(cell, source, sources, defaultSource)} /> )} diff --git a/ui/src/shared/components/RefreshingGraph.js b/ui/src/shared/components/RefreshingGraph.js index 8a8b4e7aef..a699b2c659 100644 --- a/ui/src/shared/components/RefreshingGraph.js +++ b/ui/src/shared/components/RefreshingGraph.js @@ -31,6 +31,7 @@ const RefreshingGraph = ({ onZoom, cellID, queries, + source, tableOptions, templates, timeRange, @@ -61,6 +62,7 @@ const RefreshingGraph = ({ if (type === 'single-stat') { return ( = {}) => { diff --git a/ui/test/tempVars/utils/replace.test.ts b/ui/test/tempVars/utils/replace.test.ts index 21516b2355..1441384c18 100644 --- a/ui/test/tempVars/utils/replace.test.ts +++ b/ui/test/tempVars/utils/replace.test.ts @@ -1,4 +1,4 @@ -import templateReplace, {intervalReplace} from 'src/tempVars/utils/replace' +import templateReplace, {replaceInterval} from 'src/tempVars/utils/replace' import {TemplateValueType} from 'src/types/tempVars' import {emptyTemplate} from 'test/resources' @@ -147,13 +147,13 @@ describe('templates.utils.replace', () => { }) }) - describe('intervalReplace', () => { + describe('replaceInterval', () => { it('can replace :interval:', () => { const query = `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(:interval:)` const expected = `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(46702702ms)` const pixels = 333 const durationMs = 15551999999 - const actual = intervalReplace(query, pixels, durationMs) + const actual = replaceInterval(query, pixels, durationMs) expect(actual).toBe(expected) }) @@ -164,7 +164,7 @@ describe('templates.utils.replace', () => { const pixels = 333 const durationMs = 15551999999 - const actual = intervalReplace(query, pixels, durationMs) + const actual = replaceInterval(query, pixels, durationMs) expect(actual).toBe(expected) }) @@ -189,7 +189,7 @@ describe('templates.utils.replace', () => { const durationMs = 86399999 const query = `SELECT mean(usage_idle) from "cpu" WHERE time > :dashboardTime: group by time(:interval:)` let actual = templateReplace(query, vars) - actual = intervalReplace(actual, pixels, durationMs) + actual = replaceInterval(actual, pixels, durationMs) const expected = `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 24h group by time(259459ms)` expect(actual).toBe(expected) @@ -214,7 +214,7 @@ describe('templates.utils.replace', () => { const durationMs = 3599999 const query = `SELECT mean(usage_idle) from "cpu" WHERE time > :dashboardTime: group by time(:interval:)` let actual = templateReplace(query, vars) - actual = intervalReplace(actual, pixels, durationMs) + actual = replaceInterval(actual, pixels, durationMs) const expected = `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 1h group by time(94736ms)` expect(actual).toBe(expected) @@ -224,7 +224,7 @@ describe('templates.utils.replace', () => { describe('with no :interval: present', () => { it('returns the query', () => { const expected = `SELECT mean(usage_idle) FROM "cpu" WHERE time > :dashboardTime: GROUP BY time(20ms)` - const actual = intervalReplace(expected, 10, 20000) + const actual = replaceInterval(expected, 10, 20000) expect(actual).toBe(expected) }) From 539f2413c5baaa929f33e534cee4697a9bc124ae Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Wed, 27 Jun 2018 15:46:04 -0700 Subject: [PATCH 20/59] Replace template variables in the CEO --- .../components/CellEditorOverlay.tsx | 72 +++++++++++-------- ui/src/data_explorer/actions/view/index.ts | 4 +- ui/src/shared/apis/index.ts | 27 ++++--- 3 files changed, 62 insertions(+), 41 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.tsx b/ui/src/dashboards/components/CellEditorOverlay.tsx index 949eb2805a..fe64060529 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.tsx +++ b/ui/src/dashboards/components/CellEditorOverlay.tsx @@ -21,11 +21,11 @@ import * as queryTransitions from 'src/utils/queryTransitions' import defaultQueryConfig from 'src/utils/defaultQueryConfig' import {buildQuery} from 'src/utils/influxql' import {nextSource} from 'src/dashboards/utils/sources' +import replaceTemplate, {replaceInterval} from 'src/tempVars/utils/replace' // Constants import {IS_STATIC_LEGEND} from 'src/shared/constants' import {TYPE_QUERY_CONFIG} from 'src/dashboards/constants' -import {removeUnselectedTemplateValues} from 'src/tempVars/constants' import {OVERLAY_TECHNOLOGY} from 'src/shared/constants/classNames' import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants' import { @@ -42,6 +42,7 @@ import * as DashboardsActions from 'src/types/actions/dashboards' import * as DashboardsModels from 'src/types/dashboards' import * as QueriesModels from 'src/types/queries' import * as SourcesModels from 'src/types/sources' +import {Template} from 'src/types/tempVars' type QueryTransitions = typeof queryTransitions type EditRawTextAsyncFunc = ( @@ -62,10 +63,6 @@ const staticLegend: DashboardsModels.Legend = { orientation: 'bottom', } -interface Template { - tempVar: string -} - interface QueryStatus { queryID: string status: QueriesModels.Status @@ -397,6 +394,29 @@ class CellEditorOverlay extends Component { }) } + private getConfig = async ( + url, + id: string, + query: string, + templates: Template[] + ): Promise => { + // replace all templates but :interval: + query = replaceTemplate(query, templates) + + // get durationMs to calculate interval + let queries = await getQueryConfigAndStatus(url, [{query, id}]) + const durationMs = _.get(queries, '0.durationMs', 1000) + + // calc and replace :interval: + query = replaceInterval(query, 333, durationMs) + + // fetch queryConfig for with all template variables replaced + queries = await getQueryConfigAndStatus(url, [{query, id}]) + const {queryConfig} = queries.find(q => q.id === id) + + return queryConfig + } + // The schema explorer is not built to handle user defined template variables // in the query in a clear manner. If they are being used, we indicate that in // the query config in order to disable the fields column down stream because @@ -406,44 +426,34 @@ class CellEditorOverlay extends Component { id: string, text: string ): Promise => { + const {templates} = this.props const userDefinedTempVarsInQuery = this.findUserDefinedTempVarsInQuery( text, - this.props.templates + templates ) const isUsingUserDefinedTempVars: boolean = !!userDefinedTempVarsInQuery.length try { - const selectedTempVars: Template[] = isUsingUserDefinedTempVars - ? removeUnselectedTemplateValues(userDefinedTempVarsInQuery) - : [] + const queryConfig = await this.getConfig(url, id, text, templates) - const {data} = await getQueryConfigAndStatus( - url, - [{query: text, id}], - selectedTempVars - ) + const nextQueries = this.state.queriesWorkingDraft.map(q => { + if (q.id === id) { + const isQuerySupportedByExplorer = !isUsingUserDefinedTempVars - const config = data.queries.find(q => q.id === id) - const nextQueries: QueriesModels.QueryConfig[] = this.state.queriesWorkingDraft.map( - (q: QueriesModels.QueryConfig) => { - if (q.id === id) { - const isQuerySupportedByExplorer = !isUsingUserDefinedTempVars - - if (isUsingUserDefinedTempVars) { - return {...q, rawText: text, isQuerySupportedByExplorer} - } - - return { - ...config.queryConfig, - source: q.source, - isQuerySupportedByExplorer, - } + if (isUsingUserDefinedTempVars) { + return {...q, rawText: text, isQuerySupportedByExplorer} } - return q + return { + ...queryConfig, + source: q.source, + isQuerySupportedByExplorer, + } } - ) + + return q + }) this.setState({queriesWorkingDraft: nextQueries}) } catch (error) { diff --git a/ui/src/data_explorer/actions/view/index.ts b/ui/src/data_explorer/actions/view/index.ts index 4b0cfa24bb..2d0580631b 100644 --- a/ui/src/data_explorer/actions/view/index.ts +++ b/ui/src/data_explorer/actions/view/index.ts @@ -393,13 +393,13 @@ export const editRawTextAsync = ( text: string ) => async (dispatch): Promise => { try { - const {data} = await getQueryConfigAndStatus(url, [ + const queries = await getQueryConfigAndStatus(url, [ { query: text, id, }, ]) - const config = data.queries.find(q => q.id === id) + const config = queries.find(q => q.id === id) dispatch(updateQueryConfig(config.queryConfig)) } catch (error) { dispatch(errorThrown(error)) diff --git a/ui/src/shared/apis/index.ts b/ui/src/shared/apis/index.ts index 24e9298ae6..cc4d9b1cc6 100644 --- a/ui/src/shared/apis/index.ts +++ b/ui/src/shared/apis/index.ts @@ -319,15 +319,26 @@ export function kapacitorProxy(kapacitor, method, path, body?) { }) } -export const getQueryConfigAndStatus = (url, queries, tempVars = []) => { - return AJAX({ - url, - method: 'POST', - data: {queries, tempVars}, - }) +export const getQueryConfigAndStatus = async ( + url, + queries +): Promise => { + try { + const {data} = await AJAX({ + url, + method: 'POST', + data: {queries}, + }) + + return data.queries + } catch (error) { + console.error(error) + throw error + } } -interface AnalyzeQueriesResponse { +interface AnalyzeQueriesObject { + id: string query: string duration: string queryConfig?: QueryConfig @@ -336,7 +347,7 @@ interface AnalyzeQueriesResponse { export const analyzeQueries = async ( url: string, queries: Array<{query: string}> -): Promise => { +): Promise => { try { const {data} = await AJAX({ url, From ce14f0bb5391ba4b03e766630c6c0bcd7ce5ddaf Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Thu, 28 Jun 2018 09:10:11 -0700 Subject: [PATCH 21/59] Handle case when log message is empty --- ui/src/logs/components/LogsTable.tsx | 79 ++++++++++++++++------------ ui/src/logs/utils/table.ts | 13 ++--- 2 files changed, 50 insertions(+), 42 deletions(-) diff --git a/ui/src/logs/components/LogsTable.tsx b/ui/src/logs/components/LogsTable.tsx index 13c8b96497..2d993ea43e 100644 --- a/ui/src/logs/components/LogsTable.tsx +++ b/ui/src/logs/components/LogsTable.tsx @@ -173,16 +173,19 @@ class LogsTable extends Component { width={width} scrollLeft={this.state.scrollLeft} scrollTop={this.state.scrollTop} - onScroll={this.handleGridScroll} cellRenderer={this.cellRenderer} onSectionRendered={this.handleRowRender(onRowsRendered)} + onScroll={this.handleGridScroll} columnCount={columnCount} columnWidth={this.getColumnWidth} ref={(ref: Grid) => { registerChild(ref) this.grid = ref }} - style={{height: this.calculateTotalHeight()}} + style={{ + height: this.calculateTotalHeight(), + overflowY: 'hidden', + }} /> )} @@ -194,14 +197,45 @@ class LogsTable extends Component { } private handleGridScroll = ({scrollLeft}) => { - this.handleScroll({scrollLeft, scrollTop: this.state.scrollTop}) + this.handleScroll({scrollLeft}) } - private handleRowRender = onRowsRendered => ({ - rowStartIndex, - rowStopIndex, - }) => { - onRowsRendered({startIndex: rowStartIndex, stopIndex: rowStopIndex}) + private handleScrollbarScroll = (e: MouseEvent): void => { + e.stopPropagation() + e.preventDefault() + const {scrollTop, scrollLeft} = e.target as HTMLElement + + this.handleScroll({ + scrollTop, + scrollLeft, + }) + } + + private handleScroll = scrollInfo => { + if (_.has(scrollInfo, 'scrollTop')) { + const {scrollTop} = scrollInfo + const previousTop = this.state.scrollTop + + this.setState({scrollTop}) + + if (scrollTop === 0) { + this.props.onScrolledToTop() + } else if (scrollTop !== previousTop) { + this.props.onScrollVertical() + } + } + + if (_.has(scrollInfo, 'scrollLeft')) { + const {scrollLeft} = scrollInfo + + this.setState({scrollLeft}) + } + } + + private handleRowRender = onRowsRendered => { + return ({rowStartIndex, rowStopIndex}) => { + onRowsRendered({startIndex: rowStartIndex, stopIndex: rowStopIndex}) + } } private loadMoreRows = async () => { @@ -242,15 +276,6 @@ class LogsTable extends Component { private handleHeaderScroll = ({scrollLeft}): void => this.setState({scrollLeft}) - private handleScrollbarScroll = (e: MouseEvent): void => { - const target = e.target as HTMLElement - - this.handleScroll({ - scrollTop: target.scrollTop, - scrollLeft: this.state.scrollLeft, - }) - } - private getColumnWidth = ({index}: {index: number}): number => { const column = getColumnFromData(this.props.data, index + 1) const {currentMessageWidth} = this.state @@ -270,6 +295,7 @@ class LogsTable extends Component { private calculateTotalHeight = (): number => { const data = getValuesFromData(this.props.data) + return _.reduce( data, (acc, __, index) => { @@ -284,32 +310,19 @@ class LogsTable extends Component { const columnIndex = columns.indexOf('message') const value = getValueFromData(this.props.data, index, columnIndex) - if (!value) { + if (_.isEmpty(value)) { return ROW_HEIGHT } - const lines = Math.round(value.length / this.rowCharLimit + 0.25) + const lines = Math.ceil(value.length / (this.rowCharLimit * 0.95)) - return Math.max(lines, 1) * (ROW_HEIGHT - 14) + 14 + return Math.max(lines, 1) * ROW_HEIGHT + 4 } private calculateRowHeight = ({index}: {index: number}): number => { return this.calculateMessageHeight(index) } - private handleScroll = scrollInfo => { - const {scrollLeft, scrollTop} = scrollInfo - const previousScrolltop = this.state.scrollTop - - this.setState({scrollLeft, scrollTop}) - - if (scrollTop === 0) { - this.props.onScrolledToTop() - } else if (scrollTop !== previousScrolltop) { - this.props.onScrollVertical() - } - } - private headerRenderer = ({key, style, columnIndex}) => { const column = getColumnFromData(this.props.data, columnIndex + 1) const classes = 'logs-viewer--cell logs-viewer--cell-header' diff --git a/ui/src/logs/utils/table.ts b/ui/src/logs/utils/table.ts index 886ba63991..19b1521f5a 100644 --- a/ui/src/logs/utils/table.ts +++ b/ui/src/logs/utils/table.ts @@ -32,18 +32,13 @@ export const formatColumnValue = ( case 'timestamp': return moment(+value / 1000000).format('YYYY/MM/DD HH:mm:ss') case 'message': - if (value) { - if (value.indexOf(' ') > charLimit - 5) { - return _.truncate(value, {length: charLimit - 5}).replace('\\n', '') - } else { - return value.replace('\\n', '') - } + value = (value || 'No Message Provided').replace('\\n', '') + if (value.indexOf(' ') > charLimit - 5) { + value = _.truncate(value, {length: charLimit - 5}) } - return '' - - default: return value } + return value } export const header = (key: string): string => { return getDeep( From 99377ffd76c8167983daec42bba4535feed18526 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 28 Jun 2018 10:08:48 -0700 Subject: [PATCH 22/59] Remove redundant styles --- ui/src/style/components/threesizer.scss | 125 ------------------------ 1 file changed, 125 deletions(-) diff --git a/ui/src/style/components/threesizer.scss b/ui/src/style/components/threesizer.scss index 7b2ec0ddd8..86aee8f408 100644 --- a/ui/src/style/components/threesizer.scss +++ b/ui/src/style/components/threesizer.scss @@ -189,131 +189,6 @@ $threesizer-shadow-stop: fade-out($g0-obsidian, 1); margin-top: 10px; } -.dash-graph-context--button { - width: 24px; - height: 24px; - border-radius: 3px; - font-size: 12px; - position: relative; - color: $g11-sidewalk; - margin-right: 2px; - transition: color 0.25s ease, background-color 0.25s ease; - &:hover, - &.active { - cursor: pointer; - color: $g20-white; - background-color: $g5-pepper; - } - &:last-child { - margin-right: 0; - } - >.icon { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } - &.active { - position: relative; - z-index: 20; - } -} - -.dash-graph-context--menu, -.dash-graph-context--menu.default { - z-index: 3; - position: absolute; - top: calc(100% + 8px); - left: 50%; - background-color: $g6-smoke; - transform: translateX(-50%); - border-radius: 3px; - display: flex; - flex-direction: column; - align-items: stretch; - justify-content: center; - &:before { - position: absolute; - content: ''; - border: 6px solid transparent; - border-bottom-color: $g6-smoke; - left: 50%; - top: 0; - transform: translate(-50%, -100%); - transition: border-color 0.25s ease; - } - .dash-graph-context--menu-item { - @include no-user-select(); - white-space: nowrap; - font-size: 12px; - font-weight: 700; - line-height: 26px; - height: 26px; - padding: 0 10px; - color: $g20-white; - transition: background-color 0.25s ease; - &:first-child { - border-top-left-radius: 3px; - border-top-right-radius: 3px; - } - &:last-child { - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; - } - &:hover { - background-color: $g8-storm; - cursor: pointer; - } - &.disabled, - &.disabled:hover { - cursor: default; - background-color: transparent; - font-style: italic; - color: $g11-sidewalk; - } - } -} - -.dash-graph-context--menu.primary { - background-color: $c-ocean; - &:before { - border-bottom-color: $c-ocean; - } - .dash-graph-context--menu-item:hover { - background-color: $c-pool; - } -} - -.dash-graph-context--menu.warning { - background-color: $c-star; - &:before { - border-bottom-color: $c-star; - } - .dash-graph-context--menu-item:hover { - background-color: $c-comet; - } -} - -.dash-graph-context--menu.success { - background-color: $c-rainforest; - &:before { - border-bottom-color: $c-rainforest; - } - .dash-graph-context--menu-item:hover { - background-color: $c-honeydew; - } -} - -.dash-graph-context--menu.danger { - background-color: $c-curacao; - &:before { - border-bottom-color: $c-curacao; - } - .dash-graph-context--menu-item:hover { - background-color: $c-dreamsicle; - } -} - // Header Dropdown Menu .threesizer--menu { .dropdown-menu { From 889fc439695308bb1a2ad77239d91e2b517b6a25 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 28 Jun 2018 10:09:07 -0700 Subject: [PATCH 23/59] Hide cell context items until cell hover --- ui/src/style/pages/dashboards.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/src/style/pages/dashboards.scss b/ui/src/style/pages/dashboards.scss index d061dcc2da..03b012ad11 100644 --- a/ui/src/style/pages/dashboards.scss +++ b/ui/src/style/pages/dashboards.scss @@ -211,6 +211,12 @@ $dash-graph-options-arrow: 8px; display: flex; align-items: center; flex-wrap: nowrap; + opacity: 0; + transition: opacity 0.25s ease; +} + +.dash-graph:hover .dash-graph-context { + opacity: 1; } .dash-graph-context.dash-graph-context__open { From 4b628f9ddfa733b1272098cff41691775da97b9a Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 28 Jun 2018 10:16:58 -0700 Subject: [PATCH 24/59] Move PageHeader and associated components into reusable UI directory --- ui/src/admin/containers/AdminInfluxDBPage.js | 2 +- ui/src/admin/containers/chronograf/AdminChronografPage.js | 2 +- ui/src/alerts/containers/AlertsApp.tsx | 2 +- ui/src/dashboards/components/DashboardHeader.tsx | 4 ++-- ui/src/dashboards/containers/DashboardsPage.tsx | 2 +- ui/src/data_explorer/containers/DataExplorer.tsx | 2 +- ui/src/flux/components/EmptyFluxPage.tsx | 2 +- ui/src/flux/components/FluxHeader.tsx | 2 +- ui/src/hosts/containers/HostsPage.js | 2 +- ui/src/kapacitor/components/KapacitorForm.tsx | 2 +- ui/src/kapacitor/components/KapacitorRule.tsx | 2 +- ui/src/kapacitor/components/TickscriptHeader.tsx | 2 +- ui/src/kapacitor/containers/KapacitorRulesPage.tsx | 2 +- ui/src/logs/components/LogViewerHeader.tsx | 4 ++-- .../components/page_layout}/PageHeader.tsx | 2 +- .../components/page_layout}/PageHeaderTitle.tsx | 0 ui/src/sources/containers/ManageSources.tsx | 2 +- ui/src/sources/containers/SourcePage.tsx | 2 +- ui/src/status/containers/StatusPage.tsx | 2 +- ui/test/admin/containers/AdminInfluxDBPage.test.tsx | 4 ++-- ui/test/hosts/containers/HostsPage.test.tsx | 4 ++-- ui/test/kapacitor/components/TickscriptHeader.test.tsx | 2 +- ui/test/kapacitor/containers/TickscriptPage.test.tsx | 2 +- ui/test/shared/components/PageHeader.test.tsx | 2 +- 24 files changed, 27 insertions(+), 27 deletions(-) rename ui/src/{shared/components => reusable_ui/components/page_layout}/PageHeader.tsx (95%) rename ui/src/{shared/components => reusable_ui/components/page_layout}/PageHeaderTitle.tsx (100%) diff --git a/ui/src/admin/containers/AdminInfluxDBPage.js b/ui/src/admin/containers/AdminInfluxDBPage.js index 80c5f93959..1d296dbf30 100644 --- a/ui/src/admin/containers/AdminInfluxDBPage.js +++ b/ui/src/admin/containers/AdminInfluxDBPage.js @@ -29,7 +29,7 @@ import UsersTable from 'src/admin/components/UsersTable' import RolesTable from 'src/admin/components/RolesTable' import QueriesPage from 'src/admin/containers/QueriesPage' import DatabaseManagerPage from 'src/admin/containers/DatabaseManagerPage' -import PageHeader from 'shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import FancyScrollbar from 'shared/components/FancyScrollbar' import SubSections from 'shared/components/SubSections' import {ErrorHandling} from 'src/shared/decorators/errors' diff --git a/ui/src/admin/containers/chronograf/AdminChronografPage.js b/ui/src/admin/containers/chronograf/AdminChronografPage.js index 6511914cec..c0a9f962ba 100644 --- a/ui/src/admin/containers/chronograf/AdminChronografPage.js +++ b/ui/src/admin/containers/chronograf/AdminChronografPage.js @@ -2,7 +2,7 @@ import React from 'react' import PropTypes from 'prop-types' import {connect} from 'react-redux' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import SubSections from 'src/shared/components/SubSections' import FancyScrollbar from 'shared/components/FancyScrollbar' diff --git a/ui/src/alerts/containers/AlertsApp.tsx b/ui/src/alerts/containers/AlertsApp.tsx index f839eb2385..f16cfbae06 100644 --- a/ui/src/alerts/containers/AlertsApp.tsx +++ b/ui/src/alerts/containers/AlertsApp.tsx @@ -3,7 +3,7 @@ import React, {PureComponent} from 'react' import AlertsTable from 'src/alerts/components/AlertsTable' import NoKapacitorError from 'src/shared/components/NoKapacitorError' import CustomTimeRangeDropdown from 'src/shared/components/CustomTimeRangeDropdown' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import {ErrorHandling} from 'src/shared/decorators/errors' import {getAlerts} from 'src/alerts/apis' diff --git a/ui/src/dashboards/components/DashboardHeader.tsx b/ui/src/dashboards/components/DashboardHeader.tsx index f27354b9bd..877985ceda 100644 --- a/ui/src/dashboards/components/DashboardHeader.tsx +++ b/ui/src/dashboards/components/DashboardHeader.tsx @@ -3,8 +3,8 @@ import classnames from 'classnames' import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized' -import PageHeader from 'src/shared/components/PageHeader' -import PageHeaderTitle from 'src/shared/components/PageHeaderTitle' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' +import PageHeaderTitle from 'src/reusable_ui/components/page_layout/PageHeaderTitle' import AutoRefreshDropdown from 'src/shared/components/AutoRefreshDropdown' import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown' import GraphTips from 'src/shared/components/GraphTips' diff --git a/ui/src/dashboards/containers/DashboardsPage.tsx b/ui/src/dashboards/containers/DashboardsPage.tsx index 450b370053..c13206d5d5 100644 --- a/ui/src/dashboards/containers/DashboardsPage.tsx +++ b/ui/src/dashboards/containers/DashboardsPage.tsx @@ -5,7 +5,7 @@ import download from 'src/external/download' import _ from 'lodash' import DashboardsContents from 'src/dashboards/components/DashboardsPageContents' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import {createDashboard} from 'src/dashboards/apis' import { diff --git a/ui/src/data_explorer/containers/DataExplorer.tsx b/ui/src/data_explorer/containers/DataExplorer.tsx index 18e87ce439..97101016ab 100644 --- a/ui/src/data_explorer/containers/DataExplorer.tsx +++ b/ui/src/data_explorer/containers/DataExplorer.tsx @@ -18,7 +18,7 @@ import ManualRefresh from 'src/shared/components/ManualRefresh' import AutoRefreshDropdown from 'src/shared/components/AutoRefreshDropdown' import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown' import GraphTips from 'src/shared/components/GraphTips' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import {VIS_VIEWS, AUTO_GROUP_BY, TEMPLATES} from 'src/shared/constants' import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants' diff --git a/ui/src/flux/components/EmptyFluxPage.tsx b/ui/src/flux/components/EmptyFluxPage.tsx index c03a9501c1..22adb4dc22 100644 --- a/ui/src/flux/components/EmptyFluxPage.tsx +++ b/ui/src/flux/components/EmptyFluxPage.tsx @@ -1,6 +1,6 @@ import React, {SFC} from 'react' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' interface Props { onShowOverlay: () => void diff --git a/ui/src/flux/components/FluxHeader.tsx b/ui/src/flux/components/FluxHeader.tsx index d6ac9769da..26b58cdec8 100644 --- a/ui/src/flux/components/FluxHeader.tsx +++ b/ui/src/flux/components/FluxHeader.tsx @@ -2,7 +2,7 @@ import React, {PureComponent} from 'react' import FluxOverlay from 'src/flux/components/FluxOverlay' import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import {Service} from 'src/types' diff --git a/ui/src/hosts/containers/HostsPage.js b/ui/src/hosts/containers/HostsPage.js index 1dd225119c..887cb50d8b 100644 --- a/ui/src/hosts/containers/HostsPage.js +++ b/ui/src/hosts/containers/HostsPage.js @@ -7,7 +7,7 @@ import _ from 'lodash' import HostsTable from 'src/hosts/components/HostsTable' import AutoRefreshDropdown from 'shared/components/AutoRefreshDropdown' import ManualRefresh from 'src/shared/components/ManualRefresh' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import {getCpuAndLoadForHosts, getLayouts, getAppsForHosts} from '../apis' import {getEnv} from 'src/shared/apis/env' diff --git a/ui/src/kapacitor/components/KapacitorForm.tsx b/ui/src/kapacitor/components/KapacitorForm.tsx index 2350704912..dc5d16e3b9 100644 --- a/ui/src/kapacitor/components/KapacitorForm.tsx +++ b/ui/src/kapacitor/components/KapacitorForm.tsx @@ -3,7 +3,7 @@ import React, {ChangeEvent, MouseEvent, PureComponent} from 'react' import AlertOutputs from 'src/kapacitor/components/AlertOutputs' import Input from 'src/kapacitor/components/KapacitorFormInput' import FancyScrollbar from 'src/shared/components/FancyScrollbar' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import KapacitorFormSkipVerify from 'src/kapacitor/components/KapacitorFormSkipVerify' import {Kapacitor, Source, Notification, NotificationFunc} from 'src/types' diff --git a/ui/src/kapacitor/components/KapacitorRule.tsx b/ui/src/kapacitor/components/KapacitorRule.tsx index 299d6374ef..7724adae6b 100644 --- a/ui/src/kapacitor/components/KapacitorRule.tsx +++ b/ui/src/kapacitor/components/KapacitorRule.tsx @@ -3,7 +3,7 @@ import {connect} from 'react-redux' import {bindActionCreators} from 'redux' import {InjectedRouter} from 'react-router' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import NameSection from 'src/kapacitor/components/NameSection' import ValuesSection from 'src/kapacitor/components/ValuesSection' import RuleHeaderSave from 'src/kapacitor/components/RuleHeaderSave' diff --git a/ui/src/kapacitor/components/TickscriptHeader.tsx b/ui/src/kapacitor/components/TickscriptHeader.tsx index e25914a719..1ecef3f725 100644 --- a/ui/src/kapacitor/components/TickscriptHeader.tsx +++ b/ui/src/kapacitor/components/TickscriptHeader.tsx @@ -1,6 +1,6 @@ import React, {Component} from 'react' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import LogsToggle from 'src/kapacitor/components/LogsToggle' import ConfirmButton from 'src/shared/components/ConfirmButton' import TickscriptSave, {Task} from 'src/kapacitor/components/TickscriptSave' diff --git a/ui/src/kapacitor/containers/KapacitorRulesPage.tsx b/ui/src/kapacitor/containers/KapacitorRulesPage.tsx index 3437d024b4..533bd40f88 100644 --- a/ui/src/kapacitor/containers/KapacitorRulesPage.tsx +++ b/ui/src/kapacitor/containers/KapacitorRulesPage.tsx @@ -9,7 +9,7 @@ import * as kapacitorActionCreators from '../actions/view' import KapacitorRules from 'src/kapacitor/components/KapacitorRules' import FancyScrollbar from 'src/shared/components/FancyScrollbar' import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import {Source, Kapacitor, AlertRule} from 'src/types' import {ErrorHandling} from 'src/shared/decorators/errors' diff --git a/ui/src/logs/components/LogViewerHeader.tsx b/ui/src/logs/components/LogViewerHeader.tsx index 351dcdd9c4..46add50d06 100644 --- a/ui/src/logs/components/LogViewerHeader.tsx +++ b/ui/src/logs/components/LogViewerHeader.tsx @@ -3,8 +3,8 @@ import React, {PureComponent} from 'react' import {Source, Namespace} from 'src/types' import classnames from 'classnames' import Dropdown from 'src/shared/components/Dropdown' -import PageHeader from 'src/shared/components/PageHeader' -import PageHeaderTitle from 'src/shared/components/PageHeaderTitle' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' +import PageHeaderTitle from 'src/reusable_ui/components/page_layout/PageHeaderTitle' import TimeRangeDropdown from 'src/logs/components/TimeRangeDropdown' import {TimeRange} from 'src/types' diff --git a/ui/src/shared/components/PageHeader.tsx b/ui/src/reusable_ui/components/page_layout/PageHeader.tsx similarity index 95% rename from ui/src/shared/components/PageHeader.tsx rename to ui/src/reusable_ui/components/page_layout/PageHeader.tsx index c919f557ac..166f788c0d 100644 --- a/ui/src/shared/components/PageHeader.tsx +++ b/ui/src/reusable_ui/components/page_layout/PageHeader.tsx @@ -1,7 +1,7 @@ import React, {Component, ReactElement} from 'react' import classnames from 'classnames' -import Title from 'src/shared/components/PageHeaderTitle' +import Title from 'src/reusable_ui/components/page_layout/PageHeaderTitle' import SourceIndicator from 'src/shared/components/SourceIndicator' interface Props { diff --git a/ui/src/shared/components/PageHeaderTitle.tsx b/ui/src/reusable_ui/components/page_layout/PageHeaderTitle.tsx similarity index 100% rename from ui/src/shared/components/PageHeaderTitle.tsx rename to ui/src/reusable_ui/components/page_layout/PageHeaderTitle.tsx diff --git a/ui/src/sources/containers/ManageSources.tsx b/ui/src/sources/containers/ManageSources.tsx index ab130a8dc3..526d2e711d 100644 --- a/ui/src/sources/containers/ManageSources.tsx +++ b/ui/src/sources/containers/ManageSources.tsx @@ -6,7 +6,7 @@ import * as actions from 'src/shared/actions/sources' import {notify as notifyAction} from 'src/shared/actions/notifications' import FancyScrollbar from 'src/shared/components/FancyScrollbar' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import InfluxTable from 'src/sources/components/InfluxTable' import { diff --git a/ui/src/sources/containers/SourcePage.tsx b/ui/src/sources/containers/SourcePage.tsx index e76c1fd5d3..3d626f1d81 100644 --- a/ui/src/sources/containers/SourcePage.tsx +++ b/ui/src/sources/containers/SourcePage.tsx @@ -15,7 +15,7 @@ import {connect} from 'react-redux' import Notifications from 'src/shared/components/Notifications' import SourceForm from 'src/sources/components/SourceForm' import FancyScrollbar from 'src/shared/components/FancyScrollbar' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import {DEFAULT_SOURCE} from 'src/shared/constants' const INITIAL_PATH = '/sources/new' diff --git a/ui/src/status/containers/StatusPage.tsx b/ui/src/status/containers/StatusPage.tsx index 5ec506cbf4..bc8575af4a 100644 --- a/ui/src/status/containers/StatusPage.tsx +++ b/ui/src/status/containers/StatusPage.tsx @@ -4,7 +4,7 @@ import FancyScrollbar from 'src/shared/components/FancyScrollbar' import LayoutRenderer from 'src/shared/components/LayoutRenderer' import {STATUS_PAGE_TIME_RANGE} from 'src/shared/data/timeRanges' import {AUTOREFRESH_DEFAULT} from 'src/shared/constants' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import {fixtureStatusPageCells} from 'src/status/fixtures' import {ErrorHandling} from 'src/shared/decorators/errors' diff --git a/ui/test/admin/containers/AdminInfluxDBPage.test.tsx b/ui/test/admin/containers/AdminInfluxDBPage.test.tsx index 5ef782ef61..559b1772ce 100644 --- a/ui/test/admin/containers/AdminInfluxDBPage.test.tsx +++ b/ui/test/admin/containers/AdminInfluxDBPage.test.tsx @@ -2,8 +2,8 @@ import React from 'react' import {shallow} from 'enzyme' import {DisconnectedAdminInfluxDBPage} from 'src/admin/containers/AdminInfluxDBPage' -import PageHeader from 'src/shared/components/PageHeader' -import Title from 'src/shared/components/PageHeaderTitle' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' +import Title from 'src/reusable_ui/components/page_layout/PageHeaderTitle' import {source} from 'test/resources' describe('AdminInfluxDBPage', () => { diff --git a/ui/test/hosts/containers/HostsPage.test.tsx b/ui/test/hosts/containers/HostsPage.test.tsx index 3dcfabbb86..9dd8dc6de5 100644 --- a/ui/test/hosts/containers/HostsPage.test.tsx +++ b/ui/test/hosts/containers/HostsPage.test.tsx @@ -3,8 +3,8 @@ import {shallow} from 'enzyme' import {HostsPage} from 'src/hosts/containers/HostsPage' import HostsTable from 'src/hosts/components/HostsTable' -import PageHeader from 'src/shared/components/PageHeader' -import Title from 'src/shared/components/PageHeaderTitle' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' +import Title from 'src/reusable_ui/components/page_layout/PageHeaderTitle' import {source} from 'test/resources' diff --git a/ui/test/kapacitor/components/TickscriptHeader.test.tsx b/ui/test/kapacitor/components/TickscriptHeader.test.tsx index ffbb759ded..c85faeea58 100644 --- a/ui/test/kapacitor/components/TickscriptHeader.test.tsx +++ b/ui/test/kapacitor/components/TickscriptHeader.test.tsx @@ -1,7 +1,7 @@ import React from 'react' import {mount} from 'enzyme' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import TickscriptHeader from 'src/kapacitor/components/TickscriptHeader' import TickscriptSave from 'src/kapacitor/components/TickscriptSave' import {source} from 'test/resources' diff --git a/ui/test/kapacitor/containers/TickscriptPage.test.tsx b/ui/test/kapacitor/containers/TickscriptPage.test.tsx index 656c4da10c..564dd8931b 100644 --- a/ui/test/kapacitor/containers/TickscriptPage.test.tsx +++ b/ui/test/kapacitor/containers/TickscriptPage.test.tsx @@ -2,7 +2,7 @@ import React from 'react' import {shallow} from 'enzyme' import {TickscriptPage} from 'src/kapacitor/containers/TickscriptPage' import TickscriptHeader from 'src/kapacitor/components/TickscriptHeader' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' import TickscriptSave from 'src/kapacitor/components/TickscriptSave' import {source, kapacitorRules} from 'test/resources' diff --git a/ui/test/shared/components/PageHeader.test.tsx b/ui/test/shared/components/PageHeader.test.tsx index e4f29dcaf5..0d595d87d8 100644 --- a/ui/test/shared/components/PageHeader.test.tsx +++ b/ui/test/shared/components/PageHeader.test.tsx @@ -1,7 +1,7 @@ import React from 'react' import {shallow} from 'enzyme' -import PageHeader from 'src/shared/components/PageHeader' +import PageHeader from 'src/reusable_ui/components/page_layout/PageHeader' describe('PageHeader', () => { it('should throw an error if neither titleText nor titleComponents is supplied', () => { From 774575f2aaaad193e45461c388a69f2551af5cdf Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 28 Jun 2018 10:38:17 -0700 Subject: [PATCH 25/59] Updoot changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index afbea64315..7773c30b3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ 1. [#3659](https://github.com/influxdata/chronograf/pull/3659): Upgrade Data Explorer query text field with syntax highlighting and partial multi-line support 1. [#3663](https://github.com/influxdata/chronograf/pull/3663): Truncate message preview in Alert Rules table 1. [#3770](https://github.com/influxdata/chronograf/pull/3770): Improve performance of graph crosshairs +1. [#3790](https://github.com/influxdata/chronograf/pull/3790): Hide dashboard cell menu until mouse over cell ### Bug Fixes From e6fca683ad41eac7ee79414e00bf0571993dd24e Mon Sep 17 00:00:00 2001 From: Jared Scheib Date: Thu, 28 Jun 2018 10:59:47 -0700 Subject: [PATCH 26/59] Iterate on data models for Log Viewer UI config API Co-authored-by: Alirie Gray --- server/swagger.json | 87 ++++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/server/swagger.json b/server/swagger.json index fff80a19c2..973b9d9563 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -5143,6 +5143,60 @@ } } }, + "LogViewerUIColumn": { + "description": "Contains the settings for the log viewer page UI", + "type": "object", + "required": [ + "name", + "formatting", + "mappings" + ], + "properties": { + "name": { + "description": "Unique identifier name of the column", + "type": "string" + }, + "formatting": { + "description": "Composable formatting options for the column", + "type": "array", + "items": { + "description":"Type and value of a formatting option", + "type": "object", + "required": ["type", "value"], + "properties": { + "type": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + }, + "mappings": { + "description": "Mapping from a arbitrary type and possibly name, to a value", + "type": "array", + "items": { + "description": "Type and value of a formatting option", + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string" + }, + "value": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + }, "LogViewerUIConfig": { "description": "Contains the settings for the log viewer page UI", "type": "object", @@ -5152,39 +5206,8 @@ "description": "Defines the order, names, and visibility of columns in the log viewer table", "type": "array", "items": { - "$ref": "#/definitions/RenamableField" + "$ref": "#/definitions/LogViewerUIColumn" } - }, - "columnOptions": { - "type": "object", - "required": ["severity"], - "properties": { - "severity": { - "type": "object", - "required": ["displayFormat", "colors"], - "properties": { - "displayFormat": { - "type": "string" - }, - "colors": { - "description": "Defines the display colors for each severity level", - "type": "array", - "items": { - "type": "object", - "required": ["name", "value"], - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - } - } - } - } - } } } }, From 8133fbfa5da2eed88ab5b1282791747815b40a88 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 28 Jun 2018 11:02:07 -0700 Subject: [PATCH 27/59] Fix source switching not working --- ui/src/shared/components/DatabaseList.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ui/src/shared/components/DatabaseList.tsx b/ui/src/shared/components/DatabaseList.tsx index 824eaec0ec..05f260e16d 100644 --- a/ui/src/shared/components/DatabaseList.tsx +++ b/ui/src/shared/components/DatabaseList.tsx @@ -52,7 +52,6 @@ class DatabaseList extends Component { public componentDidUpdate({ querySource: prevSource, - query: prevQuery, }: { querySource?: Source query: QueryConfig @@ -60,10 +59,6 @@ class DatabaseList extends Component { const {querySource: nextSource, query: nextQuery} = this.props const differentSource = !_.isEqual(prevSource, nextSource) - if (prevQuery.rawText === nextQuery.rawText) { - return - } - const newMetaQuery = nextQuery.rawText && nextQuery.rawText.match(/^(create|drop)/i) From f5ef55f948794eac7ee68075ab4913cc5abab6ae Mon Sep 17 00:00:00 2001 From: Alirie Gray Date: Thu, 28 Jun 2018 11:27:46 -0700 Subject: [PATCH 28/59] Add examples for LogViewerUIConfig/Column to Swagger docs --- server/swagger.json | 66 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/server/swagger.json b/server/swagger.json index 973b9d9563..880fb05bd1 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -5151,6 +5151,32 @@ "formatting", "mappings" ], + "example": { + "name": "severity", + "formatting": [ + { + "type": "text", + "value": "dotText" + } + ], + "mappings": [ + { + "type": "color", + "name": "ruby", + "value": "emergency" + }, + { + "type": "color", + "name": "rainforest", + "value": "info" + }, + { + "type": "text", + "name": "displayName", + "value": "Severity" + } + ] + }, "properties": { "name": { "description": "Unique identifier name of the column", @@ -5201,6 +5227,46 @@ "description": "Contains the settings for the log viewer page UI", "type": "object", "required": ["columns", "columnOptions"], + "example": { + "columns": [ + { + "name": "severity", + "formatting": [ + { + "type": "text", + "value": "dotText" + } + ], + "mappings": [ + { + "type": "color", + "name": "ruby", + "value": "emergency" + }, + { + "type": "color", + "name": "rainforest", + "value": "info" + }, + { + "type": "text", + "name": "displayName", + "value": "Severity" + } + ] + }, + { + "name": "messages", + "formatting": [], + "mappings": [] + }, + { + "name": "timestamp", + "formatting": [], + "mappings": [] + } + ] + }, "properties": { "columns": { "description": "Defines the order, names, and visibility of columns in the log viewer table", From 3423268de5227dd4379ebb1896f959081d73c307 Mon Sep 17 00:00:00 2001 From: Brandon Farmer Date: Thu, 28 Jun 2018 11:36:31 -0700 Subject: [PATCH 29/59] Provide empty namespace array on error fetching --- ui/src/shared/apis/databases.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/shared/apis/databases.ts b/ui/src/shared/apis/databases.ts index 68e6bd163e..8b5828d767 100644 --- a/ui/src/shared/apis/databases.ts +++ b/ui/src/shared/apis/databases.ts @@ -31,5 +31,6 @@ export const getDatabasesWithRetentionPolicies = async ( return sorted } catch (err) { console.error(err) + return [] } } From b0a037cf6923625bbfc77c5d94c3a631b6970b2d Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 28 Jun 2018 12:14:47 -0700 Subject: [PATCH 30/59] Remove concept of host from query object --- .../dashboards/components/Visualization.tsx | 7 +---- .../components/Visualization.tsx | 4 +-- ui/src/shared/components/AutoRefresh.tsx | 3 +- ui/src/shared/components/Layout.js | 29 +++++++------------ ui/src/types/queries.ts | 1 - ui/src/utils/buildQueriesForGraphs.ts | 11 +------ ui/src/utils/buildQueriesForLayouts.ts | 5 ++-- 7 files changed, 17 insertions(+), 43 deletions(-) diff --git a/ui/src/dashboards/components/Visualization.tsx b/ui/src/dashboards/components/Visualization.tsx index 52c1ca0fa4..9f56a5ebd3 100644 --- a/ui/src/dashboards/components/Visualization.tsx +++ b/ui/src/dashboards/components/Visualization.tsx @@ -1,6 +1,5 @@ import React, {SFC} from 'react' import {connect} from 'react-redux' -import _ from 'lodash' import RefreshingGraph from 'src/shared/components/RefreshingGraph' import buildQueries from 'src/utils/buildQueriesForGraphs' @@ -76,11 +75,7 @@ const DashVisualization: SFC = ({ axes={axes} type={type} tableOptions={tableOptions} - queries={buildQueries( - _.get(source, 'links.proxy'), - queryConfigs, - timeRange - )} + queries={buildQueries(queryConfigs, timeRange)} templates={templates} autoRefresh={autoRefresh} editQueryStatus={editQueryStatus} diff --git a/ui/src/data_explorer/components/Visualization.tsx b/ui/src/data_explorer/components/Visualization.tsx index 25d11a458e..c0cdb6c0d8 100644 --- a/ui/src/data_explorer/components/Visualization.tsx +++ b/ui/src/data_explorer/components/Visualization.tsx @@ -102,8 +102,8 @@ class DataExplorerVisualization extends PureComponent { } private get queries(): Query[] { - const {source, queryConfigs, timeRange} = this.props - return buildQueries(source.links.proxy, queryConfigs, timeRange) + const {queryConfigs, timeRange} = this.props + return buildQueries(queryConfigs, timeRange) } private get query(): Query { diff --git a/ui/src/shared/components/AutoRefresh.tsx b/ui/src/shared/components/AutoRefresh.tsx index 3279411d6c..4ff96023eb 100644 --- a/ui/src/shared/components/AutoRefresh.tsx +++ b/ui/src/shared/components/AutoRefresh.tsx @@ -14,7 +14,6 @@ interface Axes { } interface Query { - host: string | string[] text: string database: string db: string @@ -212,7 +211,7 @@ const AutoRefresh = ( } private queryDifference = (left, right) => { - const mapper = q => `${q.host}${q.text}` + const mapper = q => `${q.text}` const leftStrs = left.map(mapper) const rightStrs = right.map(mapper) return _.difference( diff --git a/ui/src/shared/components/Layout.js b/ui/src/shared/components/Layout.js index 4b69d8a782..fb04d8904d 100644 --- a/ui/src/shared/components/Layout.js +++ b/ui/src/shared/components/Layout.js @@ -98,32 +98,27 @@ const Layout = ( ) : ( )} @@ -132,10 +127,6 @@ const Layout = ( const {arrayOf, bool, func, number, shape, string} = PropTypes -Layout.contextTypes = { - source: shape(), -} - const propTypes = { isDragging: bool, autoRefresh: number.isRequired, diff --git a/ui/src/types/queries.ts b/ui/src/types/queries.ts index ecc1bf8c46..cf0c4b1873 100644 --- a/ui/src/types/queries.ts +++ b/ui/src/types/queries.ts @@ -1,7 +1,6 @@ import {Source} from 'src/types' export interface Query { - host: string[] // doesn't come from server - is set in buildQueriesForGraphs text: string id: string queryConfig: QueryConfig diff --git a/ui/src/utils/buildQueriesForGraphs.ts b/ui/src/utils/buildQueriesForGraphs.ts index 66133fa2c0..981d0f21ea 100644 --- a/ui/src/utils/buildQueriesForGraphs.ts +++ b/ui/src/utils/buildQueriesForGraphs.ts @@ -1,5 +1,4 @@ import _ from 'lodash' -import {getDeep} from 'src/utils/wrappers' import {buildQuery} from 'src/utils/influxql' import {TYPE_QUERY_CONFIG, TYPE_SHIFTED} from 'src/dashboards/constants' @@ -11,11 +10,7 @@ interface Statement { text: string } -const buildQueries = ( - proxy: string, - queryConfigs: QueryConfig[], - tR: TimeRange -): Query[] => { +const buildQueries = (queryConfigs: QueryConfig[], tR: TimeRange): Query[] => { const statements: Statement[] = queryConfigs.map((query: QueryConfig) => { const {rawText, range, id, shifts, database, measurement, fields} = query const timeRange: TimeRange = range || tR @@ -42,11 +37,7 @@ const buildQueries = ( const queries: Query[] = statements .filter(s => s.text !== null) .map(({queryConfig, text, id}) => { - const queryProxy = getDeep(queryConfig, 'source.links.proxy', '') - const host: string[] = [queryProxy || proxy] - return { - host, text, id, queryConfig, diff --git a/ui/src/utils/buildQueriesForLayouts.ts b/ui/src/utils/buildQueriesForLayouts.ts index 24f84c5848..ec8fc335f4 100644 --- a/ui/src/utils/buildQueriesForLayouts.ts +++ b/ui/src/utils/buildQueriesForLayouts.ts @@ -8,7 +8,7 @@ import { } from 'src/shared/constants' import {timeRanges} from 'src/shared/data/timeRanges' -import {Cell, CellQuery, LayoutQuery, Source, TimeRange} from 'src/types' +import {Cell, CellQuery, LayoutQuery, TimeRange} from 'src/types' const buildCannedDashboardQuery = ( query: LayoutQuery | CellQuery, @@ -84,7 +84,6 @@ const addTimeBoundsToRawText = (rawText: string): string => { export const buildQueriesForLayouts = ( cell: Cell, - source: Source, timeRange: TimeRange, host: string ): CellQuery[] => { @@ -117,6 +116,6 @@ export const buildQueriesForLayouts = ( queryText = buildCannedDashboardQuery(query, timeRange, host) } - return {...query, host: source.links.proxy, text: queryText} + return {...query, text: queryText} }) } From 7cc5c7e160dfcfdd2e63d48a8a5506eae409374c Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 28 Jun 2018 12:41:42 -0700 Subject: [PATCH 31/59] Remove vestigal template variable api calls --- ui/src/data_explorer/apis/index.ts | 9 ++++----- ui/src/data_explorer/components/VisHeader.tsx | 6 ++++-- ui/src/data_explorer/components/Visualization.tsx | 2 ++ ui/src/hosts/apis/index.js | 12 +++++++++--- ui/src/logs/api/index.ts | 2 -- ui/src/shared/actions/timeSeries.ts | 4 +--- ui/src/shared/apis/query.ts | 1 - ui/src/utils/queryUrlGenerator.ts | 15 ++------------- 8 files changed, 22 insertions(+), 29 deletions(-) diff --git a/ui/src/data_explorer/apis/index.ts b/ui/src/data_explorer/apis/index.ts index d933354552..b92e4094ef 100644 --- a/ui/src/data_explorer/apis/index.ts +++ b/ui/src/data_explorer/apis/index.ts @@ -6,7 +6,6 @@ import download from 'src/external/download' import {proxy} from 'src/utils/queryUrlGenerator' import {timeSeriesToTableGraph} from 'src/utils/timeSeriesTransformers' import {dataToCSV} from 'src/shared/parsing/dataToCSV' -import {TEMPLATES} from 'src/shared/constants' import {Source, QueryConfig} from 'src/types' export const writeLineProtocol = async ( @@ -28,14 +27,14 @@ interface DeprecatedQuery { } export const getDataForCSV = ( + source: Source, query: DeprecatedQuery, errorThrown ) => async () => { try { const response = await fetchTimeSeriesForCSV({ - source: query.host, + source: source.links.proxy, query: query.text, - tempVars: TEMPLATES, }) const {data} = timeSeriesToTableGraph([{response}]) @@ -47,9 +46,9 @@ export const getDataForCSV = ( } } -const fetchTimeSeriesForCSV = async ({source, query, tempVars}) => { +const fetchTimeSeriesForCSV = async ({source, query}) => { try { - const {data} = await proxy({source, query, tempVars}) + const {data} = await proxy({source, query}) return data } catch (error) { console.error(error) diff --git a/ui/src/data_explorer/components/VisHeader.tsx b/ui/src/data_explorer/components/VisHeader.tsx index 88838ddd07..3d4fbfb9cd 100644 --- a/ui/src/data_explorer/components/VisHeader.tsx +++ b/ui/src/data_explorer/components/VisHeader.tsx @@ -2,8 +2,10 @@ import React, {PureComponent} from 'react' import {getDataForCSV} from 'src/data_explorer/apis' import VisHeaderTabs from 'src/data_explorer/components/VisHeaderTabs' import {OnToggleView} from 'src/data_explorer/components/VisHeaderTab' +import {Source} from 'src/types' interface Props { + source: Source views: string[] view: string query: any @@ -13,7 +15,7 @@ interface Props { class VisHeader extends PureComponent { public render() { - const {views, view, onToggleView, query, errorThrown} = this.props + const {source, views, view, onToggleView, query, errorThrown} = this.props return (
@@ -28,7 +30,7 @@ class VisHeader extends PureComponent { {query && (
.csv diff --git a/ui/src/data_explorer/components/Visualization.tsx b/ui/src/data_explorer/components/Visualization.tsx index c0cdb6c0d8..99ef7afc3e 100644 --- a/ui/src/data_explorer/components/Visualization.tsx +++ b/ui/src/data_explorer/components/Visualization.tsx @@ -59,6 +59,7 @@ class DataExplorerVisualization extends PureComponent { public render() { const { views, + source, templates, autoRefresh, manualRefresh, @@ -73,6 +74,7 @@ class DataExplorerVisualization extends PureComponent { { - return proxy({ - source: proxyLink, - query: `SELECT mean("usage_user") FROM \":db:\".\":rp:\".\"cpu\" WHERE "cpu" = 'cpu-total' AND time > now() - 10m GROUP BY host; + const query = replaceTemplate( + `SELECT mean("usage_user") FROM \":db:\".\":rp:\".\"cpu\" WHERE "cpu" = 'cpu-total' AND time > now() - 10m GROUP BY host; SELECT mean("load1") FROM \":db:\".\":rp:\".\"system\" WHERE time > now() - 10m GROUP BY host; SELECT non_negative_derivative(mean(uptime)) AS deltaUptime FROM \":db:\".\":rp:\".\"system\" WHERE time > now() - ${telegrafSystemInterval} * 10 GROUP BY host, time(${telegrafSystemInterval}) fill(0); SELECT mean("Percent_Processor_Time") FROM \":db:\".\":rp:\".\"win_cpu\" WHERE time > now() - 10m GROUP BY host; SELECT mean("Processor_Queue_Length") FROM \":db:\".\":rp:\".\"win_system\" WHERE time > now() - 10s GROUP BY host; SELECT non_negative_derivative(mean("System_Up_Time")) AS winDeltaUptime FROM \":db:\".\":rp:\".\"win_system\" WHERE time > now() - ${telegrafSystemInterval} * 10 GROUP BY host, time(${telegrafSystemInterval}) fill(0); SHOW TAG VALUES WITH KEY = "host";`, + tempVars + ) + + return proxy({ + source: proxyLink, + query, db: telegrafDB, tempVars, }).then(resp => { diff --git a/ui/src/logs/api/index.ts b/ui/src/logs/api/index.ts index 78a070406d..8ad89d05f3 100644 --- a/ui/src/logs/api/index.ts +++ b/ui/src/logs/api/index.ts @@ -13,8 +13,6 @@ export const executeQueryAsync = async ( db: namespace.database, rp: namespace.retentionPolicy, query, - tempVars: [], - resolution: null, }) return data diff --git a/ui/src/shared/actions/timeSeries.ts b/ui/src/shared/actions/timeSeries.ts index 6d1f816647..800812b61a 100644 --- a/ui/src/shared/actions/timeSeries.ts +++ b/ui/src/shared/actions/timeSeries.ts @@ -19,7 +19,6 @@ interface Payload { query: Query db?: string rp?: string - resolution?: number } type EditQueryStatusFunction = (queryID: string, status: Status) => void @@ -79,7 +78,7 @@ const handleError = ( } export const fetchTimeSeriesAsync = async ( - {source, db, rp, query, resolution}: Payload, + {source, db, rp, query}: Payload, editQueryStatus: EditQueryStatusFunction = noop ): Promise => { handleLoading(query, editQueryStatus) @@ -89,7 +88,6 @@ export const fetchTimeSeriesAsync = async ( db, rp, query: query.text, - resolution, }) return handleSuccess(data, query, editQueryStatus) } catch (error) { diff --git a/ui/src/shared/apis/query.ts b/ui/src/shared/apis/query.ts index 8420dfa2f7..4cac8786d4 100644 --- a/ui/src/shared/apis/query.ts +++ b/ui/src/shared/apis/query.ts @@ -34,7 +34,6 @@ export const fetchTimeSeries = async ( db, rp, query: {...query, text}, - resolution, } return fetchTimeSeriesAsync(payload, editQueryStatus) diff --git a/ui/src/utils/queryUrlGenerator.ts b/ui/src/utils/queryUrlGenerator.ts index 9bc42bd2d9..08c519ba36 100644 --- a/ui/src/utils/queryUrlGenerator.ts +++ b/ui/src/utils/queryUrlGenerator.ts @@ -1,30 +1,19 @@ import AJAX from 'src/utils/ajax' interface ProxyQuery { - source: string | string[] + source: string query: string db?: string rp?: string - tempVars?: any[] - resolution?: number } -export async function proxy({ - source, - query, - db, - rp, - tempVars, - resolution, -}: ProxyQuery) { +export async function proxy({source, query, db, rp}: ProxyQuery) { try { return await AJAX({ method: 'POST', url: source, data: { - tempVars, query, - resolution, db, rp, }, From 24da2f7bef162e0bcd1f7cc7651b7edf1c56288e Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 28 Jun 2018 12:42:28 -0700 Subject: [PATCH 32/59] Remove template variable replacement from the backend --- influx/templates.go | 129 ----------- influx/templates_test.go | 490 --------------------------------------- server/queries.go | 20 +- 3 files changed, 4 insertions(+), 635 deletions(-) delete mode 100644 influx/templates.go delete mode 100644 influx/templates_test.go diff --git a/influx/templates.go b/influx/templates.go deleted file mode 100644 index 3bdefb5036..0000000000 --- a/influx/templates.go +++ /dev/null @@ -1,129 +0,0 @@ -package influx - -import ( - "regexp" - "sort" - "strconv" - "strings" - "time" - - "github.com/influxdata/chronograf" -) - -// SortTemplates the templates by size, then type, then value. -// :interval: needs to be the last template replacement -func SortTemplates(ts []chronograf.TemplateVar) []chronograf.TemplateVar { - sort.Slice(ts, func(i, j int) bool { - if ts[i].Var == ":interval:" { - return false - } - - if len(ts[i].Values) != len(ts[j].Values) { - return len(ts[i].Values) < len(ts[j].Values) - } - - if len(ts[i].Values) == 0 { - return i < j - } - - for k := range ts[i].Values { - if ts[i].Values[k].Type != ts[j].Values[k].Type { - return ts[i].Values[k].Type < ts[j].Values[k].Type - } - if ts[i].Values[k].Value != ts[j].Values[k].Value { - return ts[i].Values[k].Value < ts[j].Values[k].Value - } - } - return i < j - }) - return ts -} - -// RenderTemplate converts the template variable into a correct InfluxQL string based -// on its type -func RenderTemplate(query string, t chronograf.TemplateVar, now time.Time) (string, error) { - if len(t.Values) == 0 { - return query, nil - } - - // we only need to render the template if the template exists in the query - if !strings.Contains(query, t.Var) { - return query, nil - } - - var q string - - // First render template variable usages appearing within an InfluxQL regular expression (value should appear unquoted) - switch t.Values[0].Type { - case "tagKey", "fieldKey", "measurement", "tagValue": - r, err := regexp.Compile(`(/[.^/]*)(` + regexp.QuoteMeta(t.Var) + `)([.^/]*/)`) - - if err != nil { - return "", err - } - - q = r.ReplaceAllString(query, `${1}`+t.Values[0].Value+`${3}`) - default: - q = query - } - - // Then render template variable usages not appearing in an InfluxQL regular expression (values may be quoted) - switch t.Values[0].Type { - case "tagKey", "fieldKey", "measurement", "database": - return strings.Replace(q, t.Var, `"`+t.Values[0].Value+`"`, -1), nil - case "tagValue", "timeStamp": - return strings.Replace(q, t.Var, `'`+t.Values[0].Value+`'`, -1), nil - case "csv", "constant", "influxql": - return strings.Replace(q, t.Var, t.Values[0].Value, -1), nil - } - - tv := map[string]string{} - for i := range t.Values { - tv[t.Values[i].Type] = t.Values[i].Value - } - - if pts, ok := tv["points"]; ok { - points, err := strconv.ParseInt(pts, 0, 64) - if err != nil { - return "", err - } - - dur, err := ParseTime(query, now) - if err != nil { - return "", err - } - interval := AutoInterval(points, dur) - return strings.Replace(query, t.Var, interval, -1), nil - } - - return query, nil -} - -func AutoInterval(points int64, duration time.Duration) string { - // The function is: ((total_seconds * millisecond_converstion) / group_by) = pixels / 3 - // Number of points given the pixels - pixels := float64(points) - msPerPixel := float64(duration/time.Millisecond) / pixels - secPerPixel := float64(duration/time.Second) / pixels - if secPerPixel < 1.0 { - if msPerPixel < 1.0 { - msPerPixel = 1.0 - } - return strconv.FormatInt(int64(msPerPixel), 10) + "ms" - } - // If groupby is more than 1 second round to the second - return strconv.FormatInt(int64(secPerPixel), 10) + "s" -} - -// TemplateReplace replaces templates with values within the query string -func TemplateReplace(query string, templates []chronograf.TemplateVar, now time.Time) (string, error) { - templates = SortTemplates(templates) - for i := range templates { - var err error - query, err = RenderTemplate(query, templates[i], now) - if err != nil { - return "", err - } - } - return query, nil -} diff --git a/influx/templates_test.go b/influx/templates_test.go deleted file mode 100644 index d8e537110a..0000000000 --- a/influx/templates_test.go +++ /dev/null @@ -1,490 +0,0 @@ -package influx - -import ( - "encoding/json" - "fmt" - "reflect" - "testing" - "time" - - "github.com/influxdata/chronograf" -) - -func TestTemplateReplace(t *testing.T) { - tests := []struct { - name string - query string - vars []chronograf.TemplateVar - want string - }{ - { - name: "select with parameters", - query: ":method: field1, :field: FROM :measurement: WHERE temperature > :temperature:", - vars: []chronograf.TemplateVar{ - chronograf.TemplateVar{ - Var: ":temperature:", - Values: []chronograf.TemplateValue{ - { - Type: "csv", - Value: "10", - }, - }, - }, - chronograf.TemplateVar{ - Var: ":field:", - Values: []chronograf.TemplateValue{ - { - Type: "fieldKey", - Value: "field2", - }, - }, - }, - chronograf.TemplateVar{ - Var: ":method:", - Values: []chronograf.TemplateValue{ - { - Type: "csv", - Value: "SELECT", - }, - }, - }, - chronograf.TemplateVar{ - Var: ":measurement:", - Values: []chronograf.TemplateValue{ - { - Type: "csv", - Value: `"cpu"`, - }, - }, - }, - }, - want: `SELECT field1, "field2" FROM "cpu" WHERE temperature > 10`, - }, - { - name: "select with parameters and aggregates", - query: `SELECT mean(:field:) FROM "cpu" WHERE :tag: = :value: GROUP BY :tag:`, - vars: []chronograf.TemplateVar{ - chronograf.TemplateVar{ - Var: ":value:", - Values: []chronograf.TemplateValue{ - { - Type: "tagValue", - Value: "howdy.com", - }, - }, - }, - chronograf.TemplateVar{ - Var: ":tag:", - Values: []chronograf.TemplateValue{ - { - Type: "tagKey", - Value: "host", - }, - }, - }, - chronograf.TemplateVar{ - Var: ":field:", - Values: []chronograf.TemplateValue{ - { - Type: "fieldKey", - Value: "field", - }, - }, - }, - }, - want: `SELECT mean("field") FROM "cpu" WHERE "host" = 'howdy.com' GROUP BY "host"`, - }, - { - name: "Non-existant parameters", - query: `SELECT :field: FROM "cpu"`, - want: `SELECT :field: FROM "cpu"`, - }, - { - name: "var without a value", - query: `SELECT :field: FROM "cpu"`, - vars: []chronograf.TemplateVar{ - chronograf.TemplateVar{ - Var: ":field:", - }, - }, - want: `SELECT :field: FROM "cpu"`, - }, - { - name: "var with unknown type", - query: `SELECT :field: FROM "cpu"`, - vars: []chronograf.TemplateVar{ - chronograf.TemplateVar{ - Var: ":field:", - Values: []chronograf.TemplateValue{ - { - Type: "who knows?", - Value: "field", - }, - }, - }, - }, - want: `SELECT :field: FROM "cpu"`, - }, - { - name: "auto interval", - query: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(:interval:)`, - vars: []chronograf.TemplateVar{ - { - Var: ":interval:", - Values: []chronograf.TemplateValue{ - { - Value: "333", - Type: "points", - }, - }, - }, - }, - want: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(46702s)`, - }, - { - name: "auto interval", - query: `SELECT derivative(mean(usage_idle),:interval:) from "cpu" where time > now() - 4320h group by time(:interval:)`, - vars: []chronograf.TemplateVar{ - { - Var: ":interval:", - Values: []chronograf.TemplateValue{ - { - Value: "333", - Type: "points", - }, - }, - }, - }, - want: `SELECT derivative(mean(usage_idle),46702s) from "cpu" where time > now() - 4320h group by time(46702s)`, - }, - { - name: "auto group by", - query: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(:interval:)`, - vars: []chronograf.TemplateVar{ - { - Var: ":interval:", - Values: []chronograf.TemplateValue{ - { - Value: "333", - Type: "points", - }, - }, - }, - }, - want: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(46702s)`, - }, - { - name: "auto group by without duration", - query: `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 4320h group by time(:interval:)`, - vars: []chronograf.TemplateVar{ - { - Var: ":interval:", - Values: []chronograf.TemplateValue{ - { - Value: "333", - Type: "points", - }, - }, - }, - }, - want: `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 4320h group by time(46702s)`, - }, - { - name: "auto group by with :dashboardTime:", - query: `SELECT mean(usage_idle) from "cpu" WHERE time > :dashboardTime: group by time(:interval:)`, - vars: []chronograf.TemplateVar{ - { - Var: ":interval:", - Values: []chronograf.TemplateValue{ - { - Value: "333", - Type: "points", - }, - }, - }, - { - Var: ":dashboardTime:", - Values: []chronograf.TemplateValue{ - { - Type: "constant", - Value: "now() - 4320h", - }, - }, - }, - }, - want: `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 4320h group by time(46702s)`, - }, - { - name: "auto group by failing condition", - query: `SELECT mean(usage_idle) FROM "cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:)`, - vars: []chronograf.TemplateVar{ - { - Var: ":interval:", - Values: []chronograf.TemplateValue{ - { - Value: "38", - Type: "points", - }, - }, - }, - { - Var: ":dashboardTime:", - Values: []chronograf.TemplateValue{ - { - Value: "now() - 1h", - Type: "constant", - Selected: true, - }, - }, - }, - }, - want: `SELECT mean(usage_idle) FROM "cpu" WHERE time > now() - 1h GROUP BY time(94s)`, - }, - { - name: "no template variables specified", - query: `SELECT mean(usage_idle) FROM "cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:)`, - want: `SELECT mean(usage_idle) FROM "cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:)`, - }, - { - name: "auto group by failing condition", - query: `SELECT mean(usage_idle) FROM "cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:)`, - vars: []chronograf.TemplateVar{ - { - Var: ":interval:", - Values: []chronograf.TemplateValue{ - { - Value: "38", - Type: "points", - }, - }, - }, - { - Var: ":dashboardTime:", - Values: []chronograf.TemplateValue{ - { - Value: "now() - 1h", - Type: "constant", - Selected: true, - }, - }, - }, - }, - want: `SELECT mean(usage_idle) FROM "cpu" WHERE time > now() - 1h GROUP BY time(94s)`, - }, - { - name: "query with no template variables contained should return query", - query: `SHOW DATABASES`, - vars: []chronograf.TemplateVar{ - { - Var: ":interval:", - Values: []chronograf.TemplateValue{ - { - Value: "115", - Type: "points", - }, - }, - }, - { - Var: ":dashboardTime:", - Values: []chronograf.TemplateValue{ - { - Value: "now() - 1h", - Type: "constant", - Selected: true, - }, - }, - }, - }, - want: `SHOW DATABASES`, - }, - { - name: "query with some tagValue template variables inside a regex", - query: `SELECT "usage_active" FROM "cpu" WHERE host =~ /:host:/ AND time > :dashboardTime: FILL(null)`, - vars: []chronograf.TemplateVar{ - { - Var: ":host:", - Values: []chronograf.TemplateValue{ - { - Value: "my-host.local", - Type: "tagValue", - }, - }, - }, - { - Var: ":dashboardTime:", - Values: []chronograf.TemplateValue{ - { - Value: "now() - 1h", - Type: "constant", - Selected: true, - }, - }, - }, - }, - want: `SELECT "usage_active" FROM "cpu" WHERE host =~ /my-host.local/ AND time > now() - 1h FILL(null)`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - now, err := time.Parse(time.RFC3339, "1985-10-25T00:01:00Z") - if err != nil { - t.Fatal(err) - } - got, err := TemplateReplace(tt.query, tt.vars, now) - if err != nil { - t.Fatalf("TestParse unexpected TemplateReplace error: %v", err) - } - if got != tt.want { - t.Errorf("TestParse %s =\n%s\nwant\n%s", tt.name, got, tt.want) - } - }) - } -} - -func Test_TemplateVarsUnmarshalling(t *testing.T) { - req := `[ - { - "tempVar": ":interval:", - "values": [ - { - "value": "333", - "type": "points" - }, - { - "value": "10", - "type": "reportingInterval" - } - ] - }, - { - "tempVar": ":cpu:", - "values": [ - { - "type": "tagValue", - "value": "cpu-total", - "selected": false - } - ] - } - ]` - - want := []chronograf.TemplateVar{ - { - Var: ":interval:", - Values: []chronograf.TemplateValue{ - { - Value: "333", - Type: "points", - }, - { - Value: "10", - Type: "reportingInterval", - }, - }, - }, - { - Var: ":cpu:", - Values: []chronograf.TemplateValue{ - { - Value: "cpu-total", - Type: "tagValue", - Selected: false, - }, - }, - }, - } - - var got []chronograf.TemplateVar - err := json.Unmarshal([]byte(req), &got) - if err != nil { - t.Fatal("Err unmarshaling:", err) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("UnmarshalJSON() = \n%#v\n want \n%#v\n", got, want) - } -} - -func Test_RenderTemplate(t *testing.T) { - gbvTests := []struct { - name string - query string - want string - resolution uint // the screen resolution to render queries into - }{ - { - name: "relative time only lower bound with one day of duration", - query: "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 1d GROUP BY time(:interval:)", - resolution: 333, - want: "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 1d GROUP BY time(259s)", - }, - { - name: "relative time offset by week", - query: "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 1d - 7d AND time < now() - 7d GROUP BY time(:interval:)", - resolution: 333, - want: "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 1d - 7d AND time < now() - 7d GROUP BY time(259s)", - }, - { - name: "relative time with relative upper bound with one minute of duration", - query: "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 3m AND time < now() - 2m GROUP BY time(:interval:)", - resolution: 333, - want: "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 3m AND time < now() - 2m GROUP BY time(180ms)", - }, - { - name: "relative time with relative lower bound and now upper with one day of duration", - query: "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 1d AND time < now() GROUP BY time(:interval:)", - resolution: 333, - want: "SELECT mean(usage_idle) FROM cpu WHERE time > now() - 1d AND time < now() GROUP BY time(259s)", - }, - { - name: "absolute time with one minute of duration", - query: "SELECT mean(usage_idle) FROM cpu WHERE time > '1985-10-25T00:01:00Z' and time < '1985-10-25T00:02:00Z' GROUP BY time(:interval:)", - resolution: 333, - want: "SELECT mean(usage_idle) FROM cpu WHERE time > '1985-10-25T00:01:00Z' and time < '1985-10-25T00:02:00Z' GROUP BY time(180ms)", - }, - { - name: "absolute time with nano seconds and zero duration", - query: "SELECT mean(usage_idle) FROM cpu WHERE time > '2017-07-24T15:33:42.994Z' and time < '2017-07-24T15:33:42.994Z' GROUP BY time(:interval:)", - resolution: 333, - want: "SELECT mean(usage_idle) FROM cpu WHERE time > '2017-07-24T15:33:42.994Z' and time < '2017-07-24T15:33:42.994Z' GROUP BY time(1ms)", - }, - { - name: "subqueries render :interval:", - query: `SELECT last(sum) FROM (SELECT sum(rate) FROM (SELECT non_negative_derivative(max("counter"),1s) AS "rate" FROM "kube-infra"."monthly"."http_api_requests_total" WHERE path != '/metrics' and time > now() - 1d GROUP BY time(:interval:),* FILL(null)) GROUP BY time(:interval:))`, - resolution: 333, - want: `SELECT last(sum) FROM (SELECT sum(rate) FROM (SELECT non_negative_derivative(max("counter"),1s) AS "rate" FROM "kube-infra"."monthly"."http_api_requests_total" WHERE path != '/metrics' and time > now() - 1d GROUP BY time(259s),* FILL(null)) GROUP BY time(259s))`, - }, - { - name: "query should be returned if there are no template variables", - query: "SHOW DATABASES", - want: "SHOW DATABASES", - }, - } - - for _, tt := range gbvTests { - t.Run(tt.name, func(t *testing.T) { - now, err := time.Parse(time.RFC3339, "1985-10-25T00:01:00Z") - if err != nil { - t.Fatal(err) - } - tvar := chronograf.TemplateVar{ - Var: ":interval:", - Values: []chronograf.TemplateValue{ - { - Value: fmt.Sprintf("%d", tt.resolution), - Type: "points", - }, - }, - } - - got, err := RenderTemplate(tt.query, tvar, now) - if err != nil { - t.Fatalf("unexpected error rendering template %v", err) - } - - if got != tt.want { - t.Fatalf("%q - durations not equal! Want: %s, Got: %s", tt.name, tt.want, got) - } - }) - } -} diff --git a/server/queries.go b/server/queries.go index 936333e77c..5f533902ac 100644 --- a/server/queries.go +++ b/server/queries.go @@ -35,7 +35,6 @@ type QueryResponse struct { QueryConfig chronograf.QueryConfig `json:"queryConfig"` QueryAST *queries.SelectStatement `json:"queryAST,omitempty"` QueryTemplated *string `json:"queryTemplated,omitempty"` - TemplateVars []chronograf.TemplateVar `json:"tempVars,omitempty"` } // QueriesResponse is the response for a QueriesRequest @@ -73,13 +72,7 @@ func (s *Service) Queries(w http.ResponseWriter, r *http.Request) { Query: q.Query, } - query, err := influx.TemplateReplace(q.Query, req.TemplateVars, time.Now()) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - qc := ToQueryConfig(query) + qc := ToQueryConfig(q.Query) if err := s.DefaultRP(ctx, &qc, &src); err != nil { Error(w, http.StatusBadRequest, err.Error(), s.Logger) return @@ -87,11 +80,11 @@ func (s *Service) Queries(w http.ResponseWriter, r *http.Request) { qc.Shifts = []chronograf.TimeShift{} qr.QueryConfig = qc - if stmt, err := queries.ParseSelect(query); err == nil { + if stmt, err := queries.ParseSelect(q.Query); err == nil { qr.QueryAST = stmt } - if dur, err := influx.ParseTime(query, time.Now()); err == nil { + if dur, err := influx.ParseTime(q.Query, time.Now()); err == nil { ms := dur.Nanoseconds() / int64(time.Millisecond) if ms == 0 { ms = 1 @@ -100,12 +93,7 @@ func (s *Service) Queries(w http.ResponseWriter, r *http.Request) { qr.Duration = ms } - if len(req.TemplateVars) > 0 { - qr.TemplateVars = req.TemplateVars - qr.QueryConfig.RawText = &qr.Query - qr.QueryTemplated = &query - } - + qr.QueryConfig.RawText = &qr.Query qr.QueryConfig.ID = q.ID res.Queries[i] = qr } From 65b803f86fad5895ddf229ff23b82b3745081c17 Mon Sep 17 00:00:00 2001 From: Alirie Gray Date: Thu, 28 Jun 2018 13:05:10 -0700 Subject: [PATCH 33/59] Simplify LogViewerUIColumn Swagger definition Combine mappings and formatting into "encoding." Co-authored-by: Jared Scheib --- server/swagger.json | 139 ++++++++++++++++++++------------------------ 1 file changed, 63 insertions(+), 76 deletions(-) diff --git a/server/swagger.json b/server/swagger.json index 880fb05bd1..381a7f316e 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -5148,67 +5148,20 @@ "type": "object", "required": [ "name", - "formatting", - "mappings" + "encoding" ], - "example": { - "name": "severity", - "formatting": [ - { - "type": "text", - "value": "dotText" - } - ], - "mappings": [ - { - "type": "color", - "name": "ruby", - "value": "emergency" - }, - { - "type": "color", - "name": "rainforest", - "value": "info" - }, - { - "type": "text", - "name": "displayName", - "value": "Severity" - } - ] - }, "properties": { "name": { "description": "Unique identifier name of the column", "type": "string" }, - "formatting": { - "description": "Composable formatting options for the column", + "encoding": { + "description": "Composable encoding options for the column", "type": "array", "items": { - "description":"Type and value of a formatting option", + "description":"Type and value and optional name of an encoding", "type": "object", "required": ["type", "value"], - "properties": { - "type": { - "type": "string" - }, - "value": { - "type": "string" - } - } - } - }, - "mappings": { - "description": "Mapping from a arbitrary type and possibly name, to a value", - "type": "array", - "items": { - "description": "Type and value of a formatting option", - "type": "object", - "required": [ - "type", - "value" - ], "properties": { "type": { "type": "string" @@ -5221,23 +5174,69 @@ } } } + }, + "example": { + "name": "severity", + "encoding": [ + { + "type": "label", + "value": "icon" + }, + { + "type": "label", + "value": "text" + }, + { + "type": "visibility", + "value": "visible" + }, + { + "type": "color", + "name": "ruby", + "value": "emergency" + }, + { + "type": "color", + "name": "rainforest", + "value": "info" + }, + { + "type": "displayName", + "value": "Log Severity!" + } + ] } - }, - "LogViewerUIConfig": { + }, + "LogViewerUIConfig": { "description": "Contains the settings for the log viewer page UI", "type": "object", - "required": ["columns", "columnOptions"], + "required": ["columns"], + "properties": { + "columns": { + "description": "Defines the order, names, and visibility of columns in the log viewer table", + "type": "array", + "items": { + "$ref": "#/definitions/LogViewerUIColumn" + } + } + }, "example": { "columns": [ { "name": "severity", - "formatting": [ + "encoding": [ { - "type": "text", - "value": "dotText" - } - ], - "mappings": [ + "type": "label", + "value": "icon" + }, + { + "type": "label", + "value": "text" + }, + { + "type": "visibility", + "value": "visible" + }, { "type": "color", "name": "ruby", @@ -5249,32 +5248,20 @@ "value": "info" }, { - "type": "text", - "name": "displayName", - "value": "Severity" + "type": "displayName", + "value": "Log Severity!" } ] }, { "name": "messages", - "formatting": [], - "mappings": [] + "encoding": [] }, { "name": "timestamp", - "formatting": [], - "mappings": [] + "encoding": [] } ] - }, - "properties": { - "columns": { - "description": "Defines the order, names, and visibility of columns in the log viewer table", - "type": "array", - "items": { - "$ref": "#/definitions/LogViewerUIColumn" - } - } } }, "AuthConfig": { From 4204700d4967a3454b3efdf9ad93de2bdbc6dd1f Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 28 Jun 2018 13:19:32 -0700 Subject: [PATCH 34/59] Remove tempVars from backend queries response --- chronograf.go | 19 +++++----- influx/influx.go | 8 ----- influx/influx_test.go | 13 +------ server/queries.go | 1 - server/queries_test.go | 81 ------------------------------------------ 5 files changed, 10 insertions(+), 112 deletions(-) diff --git a/chronograf.go b/chronograf.go index b26c3bea0d..e58cd199ba 100644 --- a/chronograf.go +++ b/chronograf.go @@ -184,16 +184,15 @@ type Template struct { // Query retrieves a Response from a TimeSeries. type Query struct { - Command string `json:"query"` // Command is the query itself - DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. - RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. - Epoch string `json:"epoch,omitempty"` // Epoch is the time format for the return results - TemplateVars []TemplateVar `json:"tempVars,omitempty"` // TemplateVars are template variables to replace within an InfluxQL query - Wheres []string `json:"wheres,omitempty"` // Wheres restricts the query to certain attributes - GroupBys []string `json:"groupbys,omitempty"` // GroupBys collate the query by these tags - Resolution uint `json:"resolution,omitempty"` // Resolution is the available screen resolution to render query results - Label string `json:"label,omitempty"` // Label is the Y-Axis label for the data - Range *Range `json:"range,omitempty"` // Range is the default Y-Axis range for the data + Command string `json:"query"` // Command is the query itself + DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. + RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. + Epoch string `json:"epoch,omitempty"` // Epoch is the time format for the return results + Wheres []string `json:"wheres,omitempty"` // Wheres restricts the query to certain attributes + GroupBys []string `json:"groupbys,omitempty"` // GroupBys collate the query by these tags + Resolution uint `json:"resolution,omitempty"` // Resolution is the available screen resolution to render query results + Label string `json:"label,omitempty"` // Label is the Y-Axis label for the data + Range *Range `json:"range,omitempty"` // Range is the default Y-Axis range for the data } // DashboardQuery includes state for the query builder. This is a transition diff --git a/influx/influx.go b/influx/influx.go index 6c28f43867..511b9da09f 100644 --- a/influx/influx.go +++ b/influx/influx.go @@ -10,7 +10,6 @@ import ( "net/http" "net/url" "strings" - "time" "github.com/influxdata/chronograf" ) @@ -55,13 +54,6 @@ func (c *Client) query(u *url.URL, q chronograf.Query) (chronograf.Response, err } req.Header.Set("Content-Type", "application/json") command := q.Command - // TODO(timraymond): move this upper Query() function - if len(q.TemplateVars) > 0 { - command, err = TemplateReplace(q.Command, q.TemplateVars, time.Now()) - if err != nil { - return nil, err - } - } logs := c.Logger. WithField("component", "proxy"). WithField("host", req.Host). diff --git a/influx/influx_test.go b/influx/influx_test.go index f92980c029..980b0e4d6e 100644 --- a/influx/influx_test.go +++ b/influx/influx_test.go @@ -277,18 +277,7 @@ func Test_Influx_HTTPS_InsecureSkipVerify(t *testing.T) { called = false q = "" query = chronograf.Query{ - Command: "select :field: from cpu", - TemplateVars: []chronograf.TemplateVar{ - chronograf.TemplateVar{ - Var: ":field:", - Values: []chronograf.TemplateValue{ - { - Value: "usage_user", - Type: "fieldKey", - }, - }, - }, - }, + Command: `select "usage_user" from cpu`, } _, err = series.Query(ctx, query) if err != nil { diff --git a/server/queries.go b/server/queries.go index 5f533902ac..354fe2bf59 100644 --- a/server/queries.go +++ b/server/queries.go @@ -93,7 +93,6 @@ func (s *Service) Queries(w http.ResponseWriter, r *http.Request) { qr.Duration = ms } - qr.QueryConfig.RawText = &qr.Query qr.QueryConfig.ID = q.ID res.Queries[i] = qr } diff --git a/server/queries_test.go b/server/queries_test.go index 86aa4358ac..8225d2d93a 100644 --- a/server/queries_test.go +++ b/server/queries_test.go @@ -82,87 +82,6 @@ func TestService_Queries(t *testing.T) { } ]}`))), want: `{"queries":[{"durationMs":0,"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","query":"SHOW DATABASES","queryConfig":{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","database":"","measurement":"","retentionPolicy":"","fields":[],"tags":{},"groupBy":{"time":"","tags":[]},"areTagsAccepted":false,"rawText":"SHOW DATABASES","range":null,"shifts":[]}}]} -`, - }, - { - name: "query with template vars", - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: ID, - }, nil - }, - }, - ID: "1", - w: httptest.NewRecorder(), - r: httptest.NewRequest("POST", "/queries", bytes.NewReader([]byte(`{ - "queries": [ - { - "query": "SELECT \"pingReq\" FROM :dbs:.\"monitor\".\"httpd\" WHERE time > :dashboardTime: AND time < :upperDashboardTime: GROUP BY time(:interval:)", - "id": "82b60d37-251e-4afe-ac93-ca20a3642b11" - } - ], - "tempVars": [ - { - "tempVar": ":dbs:", - "values": [ - { - "value": "_internal", - "type": "database", - "selected": true - } - ], - "id": "792eda0d-2bb2-4de6-a86f-1f652889b044", - "type": "databases", - "label": "", - "query": { - "influxql": "SHOW DATABASES", - "measurement": "", - "tagKey": "", - "fieldKey": "" - }, - "links": { - "self": "/chronograf/v1/dashboards/1/templates/792eda0d-2bb2-4de6-a86f-1f652889b044" - } - }, - { - "id": "dashtime", - "tempVar": ":dashboardTime:", - "type": "constant", - "values": [ - { - "value": "now() - 15m", - "type": "constant", - "selected": true - } - ] - }, - { - "id": "upperdashtime", - "tempVar": ":upperDashboardTime:", - "type": "constant", - "values": [ - { - "value": "now()", - "type": "constant", - "selected": true - } - ] - }, - { - "id": "interval", - "type": "constant", - "tempVar": ":interval:", - "values": [ - { - "value": "333", - "type": "points" - } - ] - } - ] - }`))), - want: `{"queries":[{"durationMs":899999,"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","query":"SELECT \"pingReq\" FROM :dbs:.\"monitor\".\"httpd\" WHERE time \u003e :dashboardTime: AND time \u003c :upperDashboardTime: GROUP BY time(:interval:)","queryConfig":{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","database":"","measurement":"","retentionPolicy":"","fields":[],"tags":{},"groupBy":{"time":"","tags":[]},"areTagsAccepted":false,"rawText":"SELECT \"pingReq\" FROM :dbs:.\"monitor\".\"httpd\" WHERE time \u003e :dashboardTime: AND time \u003c :upperDashboardTime: GROUP BY time(:interval:)","range":null,"shifts":[]},"queryTemplated":"SELECT \"pingReq\" FROM \"_internal\".\"monitor\".\"httpd\" WHERE time \u003e now() - 15m AND time \u003c now() GROUP BY time(2s)","tempVars":[{"tempVar":":upperDashboardTime:","values":[{"value":"now()","type":"constant","selected":true}]},{"tempVar":":dashboardTime:","values":[{"value":"now() - 15m","type":"constant","selected":true}]},{"tempVar":":dbs:","values":[{"value":"_internal","type":"database","selected":true}]},{"tempVar":":interval:","values":[{"value":"333","type":"points","selected":false}]}]}]} `, }, } From 22fc703079af63c4d37a8ec653def4e5c46f8c57 Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Wed, 27 Jun 2018 15:14:35 -0700 Subject: [PATCH 35/59] Dynamically render severity colors in log viewer In preparation of #3753. --- ui/package.json | 2 + ui/src/logs/components/LogsTable.tsx | 17 ++- ui/src/logs/containers/LogsPage.tsx | 2 + ui/src/logs/utils/colors.ts | 16 +++ ui/src/shared/components/HistogramChart.tsx | 5 +- .../shared/components/HistogramChartBars.tsx | 16 ++- .../components/HistogramChartTooltip.tsx | 6 +- ui/src/style/components/histogram-chart.scss | 2 - ui/src/style/pages/logs-viewer.scss | 118 ------------------ ui/yarn.lock | 6 +- 10 files changed, 61 insertions(+), 129 deletions(-) create mode 100644 ui/src/logs/utils/colors.ts diff --git a/ui/package.json b/ui/package.json index 3ec1c8abab..0a70de32d8 100644 --- a/ui/package.json +++ b/ui/package.json @@ -37,6 +37,7 @@ "@types/chai": "^4.1.2", "@types/chroma-js": "^1.3.4", "@types/codemirror": "^0.0.56", + "@types/d3-color": "^1.2.1", "@types/d3-scale": "^2.0.1", "@types/dygraphs": "^1.1.6", "@types/enzyme": "^3.1.9", @@ -129,6 +130,7 @@ "chroma-js": "^1.3.6", "classnames": "^2.2.3", "codemirror": "^5.36.0", + "d3-color": "^1.2.0", "d3-scale": "^2.1.0", "dygraphs": "2.1.0", "enzyme-adapter-react-16": "^1.1.1", diff --git a/ui/src/logs/components/LogsTable.tsx b/ui/src/logs/components/LogsTable.tsx index 2d993ea43e..2ca61ee1ca 100644 --- a/ui/src/logs/components/LogsTable.tsx +++ b/ui/src/logs/components/LogsTable.tsx @@ -1,11 +1,14 @@ import _ from 'lodash' import moment from 'moment' import classnames from 'classnames' -import React, {Component, MouseEvent} from 'react' +import React, {Component, MouseEvent, CSSProperties} from 'react' import {Grid, AutoSizer, InfiniteLoader} from 'react-virtualized' +import {color} from 'd3-color' + import FancyScrollbar from 'src/shared/components/FancyScrollbar' import {getDeep} from 'src/utils/wrappers' +import {colorForSeverity} from 'src/logs/utils/colors' import { getColumnFromData, getValueFromData, @@ -346,6 +349,7 @@ class LogsTable extends Component { title={value} onMouseOver={this.handleMouseEnter} data-index={rowIndex} + style={this.severityDotStyle(value)} /> ) } else { @@ -395,6 +399,17 @@ class LogsTable extends Component { ) } + private severityDotStyle = (severity: string): CSSProperties => { + const severityColor = colorForSeverity(severity) + const brightSeverityColor = color(severityColor) + .brighter(0.5) + .hex() + + return { + background: `linear-gradient(45deg, ${severityColor}, ${brightSeverityColor}`, + } + } + private handleMouseEnter = (e: MouseEvent): void => { const target = e.target as HTMLElement this.setState({currentRow: +target.dataset.index}) diff --git a/ui/src/logs/containers/LogsPage.tsx b/ui/src/logs/containers/LogsPage.tsx index 4a46486d2f..868f6766b4 100644 --- a/ui/src/logs/containers/LogsPage.tsx +++ b/ui/src/logs/containers/LogsPage.tsx @@ -24,6 +24,7 @@ import SearchBar from 'src/logs/components/LogsSearchBar' import FilterBar from 'src/logs/components/LogsFilterBar' import LogsTable from 'src/logs/components/LogsTable' import {getDeep} from 'src/utils/wrappers' +import {colorForSeverity} from 'src/logs/utils/colors' import {Source, Namespace, TimeRange, RemoteDataState} from 'src/types' import {Filter} from 'src/types/logs' @@ -189,6 +190,7 @@ class LogsPage extends PureComponent { dataStatus={histogramDataStatus} width={width} height={height} + colorScale={colorForSeverity} onZoom={this.handleChartZoom} /> )} diff --git a/ui/src/logs/utils/colors.ts b/ui/src/logs/utils/colors.ts new file mode 100644 index 0000000000..d59c6e4a42 --- /dev/null +++ b/ui/src/logs/utils/colors.ts @@ -0,0 +1,16 @@ +const SEVERITY_COLORS = { + emergency: '#BF3D5E', + alert: '#DC4E58', + critical: '#F95F53', + error: '#F48D38', + warning: '#FFB94A', + notice: '#4ED8A0', + info: '#7A65F2', + debug: '#8E91A1', +} + +const DEFAULT_SEVERITY_COLOR = '#7A65F2' + +export const colorForSeverity = (severity: string): string => { + return SEVERITY_COLORS[severity] || DEFAULT_SEVERITY_COLOR +} diff --git a/ui/src/shared/components/HistogramChart.tsx b/ui/src/shared/components/HistogramChart.tsx index 2ec3fb4c7c..6b00555b86 100644 --- a/ui/src/shared/components/HistogramChart.tsx +++ b/ui/src/shared/components/HistogramChart.tsx @@ -35,6 +35,7 @@ interface Props { dataStatus: RemoteDataState width: number height: number + colorScale: (group: string) => string onZoom: (TimePeriod) => void } @@ -53,7 +54,7 @@ class HistogramChart extends PureComponent { } public render() { - const {width, height, data} = this.props + const {width, height, data, colorScale} = this.props const {margins} = this if (width === 0 || height === 0) { @@ -122,6 +123,8 @@ class HistogramChart extends PureComponent { data={data} xScale={xScale} yScale={yScale} + hoverDatum={hoverDatum} + colorScale={colorScale} /> diff --git a/ui/src/shared/components/HistogramChartBars.tsx b/ui/src/shared/components/HistogramChartBars.tsx index bbf388d47f..aff4b4b6ff 100644 --- a/ui/src/shared/components/HistogramChartBars.tsx +++ b/ui/src/shared/components/HistogramChartBars.tsx @@ -1,11 +1,13 @@ import React, {PureComponent} from 'react' import _ from 'lodash' import {ScaleLinear, ScaleTime} from 'd3-scale' +import {color} from 'd3-color' import {HistogramData, HistogramDatum} from 'src/types/histogram' const BAR_BORDER_RADIUS = 4 const BAR_PADDING_SIDES = 4 +const HOVER_BRIGTHEN_FACTOR = 0.4 interface Props { width: number @@ -13,6 +15,8 @@ interface Props { data: HistogramData xScale: ScaleTime yScale: ScaleLinear + colorScale: (group: string) => string + hoverDatum?: HistogramDatum } class HistogramChartBars extends PureComponent { @@ -42,6 +46,7 @@ class HistogramChartBars extends PureComponent { y={d.y} width={d.width} height={d.height} + fill={d.fill} clipPath={`url(#histogram-chart-bars--clip-${key})`} data-group={d.group} data-key={d.key} @@ -53,7 +58,7 @@ class HistogramChartBars extends PureComponent { } private get renderData() { - const {data, xScale, yScale} = this.props + const {data, xScale, yScale, colorScale, hoverDatum} = this.props const {barWidth, sortFn} = this const visibleData = data.filter(d => d.value !== 0) @@ -84,6 +89,14 @@ class HistogramChartBars extends PureComponent { group.forEach((d: HistogramDatum) => { const height = yScale(0) - yScale(d.value) + let fill = colorScale(d.group) + + if (!!hoverDatum && hoverDatum.key === d.key) { + fill = color(fill) + .brighter(HOVER_BRIGTHEN_FACTOR) + .hex() + } + renderData.bars.push({ key: d.key, group: d.group, @@ -91,6 +104,7 @@ class HistogramChartBars extends PureComponent { y: yScale(d.value) - offset, width: barWidth, height, + fill, }) offset += height diff --git a/ui/src/shared/components/HistogramChartTooltip.tsx b/ui/src/shared/components/HistogramChartTooltip.tsx index d5b2e3e917..c0dc4535ad 100644 --- a/ui/src/shared/components/HistogramChartTooltip.tsx +++ b/ui/src/shared/components/HistogramChartTooltip.tsx @@ -28,11 +28,7 @@ const HistogramChartTooltip: SFC = props => { } return ( -
+
{datum.value}
{datum.group}
diff --git a/ui/src/style/components/histogram-chart.scss b/ui/src/style/components/histogram-chart.scss index c04e41a993..c253ff85de 100644 --- a/ui/src/style/components/histogram-chart.scss +++ b/ui/src/style/components/histogram-chart.scss @@ -35,10 +35,8 @@ .histogram-chart-bars--bar { shape-rendering: crispEdges; - fill: $c-amethyst; opacity: 1; pointer: cursor; - shape-rendering: crispEdges; } .histogram-chart--axes, .histogram-chart-skeleton { diff --git a/ui/src/style/pages/logs-viewer.scss b/ui/src/style/pages/logs-viewer.scss index 2b59b4722e..6cac8e0fcc 100644 --- a/ui/src/style/pages/logs-viewer.scss +++ b/ui/src/style/pages/logs-viewer.scss @@ -9,23 +9,6 @@ $logs-viewer-filter-height: 42px; $logs-viewer-results-text-indent: 33px; $logs-viewer-gutter: 60px; -$severity-emerg: $c-ruby; -$severity-alert: $c-fire; -$severity-crit: $c-curacao; -$severity-err: $c-tiger; -$severity-warning: $c-pineapple; -$severity-notice: $c-rainforest; -$severity-info: $c-star; -$severity-debug: $g9-mountain; -$severity-emerg-intense: $c-fire; -$severity-alert-intense: $c-curacao; -$severity-crit-intense: $c-tiger; -$severity-err-intense: $c-pineapple; -$severity-warning-intense: $c-thunder; -$severity-notice-intense: $c-honeydew; -$severity-info-intense: $c-comet; -$severity-debug-intense: $g10-wolf; - .logs-viewer { display: flex; flex-direction: column; @@ -247,31 +230,6 @@ $severity-debug-intense: $g10-wolf; background-color: $g0-obsidian; border: 2px solid $g3-castle; margin-left: 2px; - - &.emerg-severity { - @include gradient-diag-up($severity-emerg, $severity-emerg-intense); - } - &.alert-severity { - @include gradient-diag-up($severity-alert, $severity-alert-intense); - } - &.crit-severity { - @include gradient-diag-up($severity-crit, $severity-crit-intense); - } - &.err-severity { - @include gradient-diag-up($severity-err, $severity-err-intense); - } - &.warning-severity { - @include gradient-diag-up($severity-warning, $severity-warning-intense); - } - &.notice-severity { - @include gradient-diag-up($severity-notice, $severity-notice-intense); - } - &.info-severity { - @include gradient-diag-up($severity-info, $severity-info-intense); - } - &.debug-severity { - @include gradient-diag-up($severity-debug, $severity-debug-intense); - } } // Play & Pause Toggle in Header @@ -327,79 +285,3 @@ $severity-debug-intense: $g10-wolf; background-color: $c-laser; } } - -.logs-viewer .histogram-chart-bars--bar, .logs-viewer .histogram-chart-tooltip { - &[data-group="emerg"] { - fill: $severity-emerg; - color: $severity-emerg; - } - - &[data-group="alert"] { - fill: $severity-alert; - color: $severity-alert; - } - - &[data-group="crit"] { - fill: $severity-crit; - color: $severity-crit; - } - - &[data-group="err"] { - fill: $severity-err; - color: $severity-err; - } - - &[data-group="warning"] { - fill: $severity-warning; - color: $severity-warning; - } - - &[data-group="notice"] { - fill: $severity-notice; - color: $severity-notice; - } - - &[data-group="info"] { - fill: $severity-info; - color: $severity-info; - } - - &[data-group="debug"] { - fill: $severity-debug; - color: $severity-debug; - } -} - -.logs-viewer .histogram-chart-bars--bar:hover { - &[data-group="emerg"] { - fill: $severity-emerg-intense; - } - - &[data-group="alert"] { - fill: $severity-alert-intense; - } - - &[data-group="crit"] { - fill: $severity-crit-intense; - } - - &[data-group="err"] { - fill: $severity-err-intense; - } - - &[data-group="warning"] { - fill: $severity-warning-intense; - } - - &[data-group="notice"] { - fill: $severity-notice-intense; - } - - &[data-group="info"] { - fill: $severity-info-intense; - } - - &[data-group="debug"] { - fill: $severity-debug-intense; - } -} diff --git a/ui/yarn.lock b/ui/yarn.lock index 404ab1dc10..ce46ea6e35 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -32,6 +32,10 @@ version "0.0.56" resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.56.tgz#1fcf68df0d0a49791d843dadda7d94891ac88669" +"@types/d3-color@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-1.2.1.tgz#26141c3c554e320edd40726b793570a3ae57397e" + "@types/d3-scale@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-2.0.1.tgz#f94cd991c50422b2e68d8f43be3f9fffdb1ae7be" @@ -2624,7 +2628,7 @@ d3-collection@1: version "1.0.4" resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2" -d3-color@1: +d3-color@1, d3-color@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.2.0.tgz#d1ea19db5859c86854586276ec892cf93148459a" From fef0075cbcf49886d69958e54b5ad2d2204c3f3f Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Wed, 27 Jun 2018 16:32:31 -0700 Subject: [PATCH 36/59] Show entire bar group in HistogramChartTooltip --- ui/src/shared/components/HistogramChart.tsx | 67 +++------------ .../shared/components/HistogramChartBars.tsx | 82 ++++++++++++++++--- .../components/HistogramChartTooltip.tsx | 39 +++++---- ui/src/style/components/histogram-chart.scss | 12 +-- ui/src/types/histogram.ts | 9 ++ .../shared/components/HistogramChart.test.tsx | 10 ++- .../HistogramChart.test.tsx.snap | 72 +++++++++++----- 7 files changed, 176 insertions(+), 115 deletions(-) diff --git a/ui/src/shared/components/HistogramChart.tsx b/ui/src/shared/components/HistogramChart.tsx index 6b00555b86..820dc79ecc 100644 --- a/ui/src/shared/components/HistogramChart.tsx +++ b/ui/src/shared/components/HistogramChart.tsx @@ -1,4 +1,4 @@ -import React, {PureComponent, MouseEvent} from 'react' +import React, {PureComponent} from 'react' import _ from 'lodash' import {scaleLinear, scaleTime, ScaleLinear, ScaleTime} from 'd3-scale' @@ -9,20 +9,16 @@ import HistogramChartSkeleton from 'src/shared/components/HistogramChartSkeleton import XBrush from 'src/shared/components/XBrush' import extentBy from 'src/utils/extentBy' -import {getDeep} from 'src/utils/wrappers' import {RemoteDataState} from 'src/types' import { TimePeriod, HistogramData, - HistogramDatum, Margins, - TooltipAnchor, + HoverData, } from 'src/types/histogram' const PADDING_TOP = 0.2 -const TOOLTIP_HORIZONTAL_MARGIN = 5 -const TOOLTIP_REFLECT_DIST = 100 // Rather than use these magical constants, we could also render a digit and // capture its measured width with as state before rendering anything else. @@ -40,17 +36,14 @@ interface Props { } interface State { - hoverX: number - hoverY: number - hoverDatum?: HistogramDatum - hoverAnchor: TooltipAnchor + hoverData?: HoverData } class HistogramChart extends PureComponent { constructor(props) { super(props) - this.state = {hoverX: -1, hoverY: -1, hoverAnchor: 'left'} + this.state = {} } public render() { @@ -71,7 +64,7 @@ class HistogramChart extends PureComponent { ) } - const {hoverDatum, hoverX, hoverY, hoverAnchor} = this.state + const {hoverData} = this.state const { xScale, yScale, @@ -87,8 +80,6 @@ class HistogramChart extends PureComponent { width={width} height={height} className={`histogram-chart ${loadingClass}`} - onMouseOver={this.handleMouseMove} - onMouseOut={this.handleMouseOut} > @@ -123,17 +114,15 @@ class HistogramChart extends PureComponent { data={data} xScale={xScale} yScale={yScale} - hoverDatum={hoverDatum} colorScale={colorScale} + hoverData={hoverData} + onHover={this.handleHover} /> - + {hoverData && ( + + )} ) } @@ -207,41 +196,11 @@ class HistogramChart extends PureComponent { private handleBrush = (t: TimePeriod): void => { this.props.onZoom(t) - this.setState({hoverDatum: null}) + this.setState({hoverData: null}) } - private handleMouseMove = (e: MouseEvent): void => { - const key = getDeep(e, 'target.dataset.key', '') - - if (!key) { - return - } - - const {data} = this.props - const hoverDatum = data.find(d => d.key === key) - - if (!hoverDatum) { - return - } - - const bar = e.target as SVGRectElement - const barRect = bar.getBoundingClientRect() - const barRectHeight = barRect.bottom - barRect.top - const hoverY = barRect.top + barRectHeight / 2 - - let hoverX = barRect.right + TOOLTIP_HORIZONTAL_MARGIN - let hoverAnchor: TooltipAnchor = 'left' - - if (hoverX >= window.innerWidth - TOOLTIP_REFLECT_DIST) { - hoverX = window.innerWidth - barRect.left + TOOLTIP_HORIZONTAL_MARGIN - hoverAnchor = 'right' - } - - this.setState({hoverDatum, hoverX, hoverY, hoverAnchor}) - } - - private handleMouseOut = (): void => { - this.setState({hoverDatum: null}) + private handleHover = (hoverData: HoverData): void => { + this.setState({hoverData}) } } diff --git a/ui/src/shared/components/HistogramChartBars.tsx b/ui/src/shared/components/HistogramChartBars.tsx index aff4b4b6ff..8b7ecfe4cf 100644 --- a/ui/src/shared/components/HistogramChartBars.tsx +++ b/ui/src/shared/components/HistogramChartBars.tsx @@ -1,13 +1,22 @@ -import React, {PureComponent} from 'react' +import React, {PureComponent, MouseEvent} from 'react' import _ from 'lodash' import {ScaleLinear, ScaleTime} from 'd3-scale' import {color} from 'd3-color' -import {HistogramData, HistogramDatum} from 'src/types/histogram' +import {getDeep} from 'src/utils/wrappers' + +import { + HistogramData, + HistogramDatum, + HoverData, + TooltipAnchor, +} from 'src/types/histogram' const BAR_BORDER_RADIUS = 4 const BAR_PADDING_SIDES = 4 const HOVER_BRIGTHEN_FACTOR = 0.4 +const TOOLTIP_HORIZONTAL_MARGIN = 5 +const TOOLTIP_REFLECT_DIST = 100 interface Props { width: number @@ -16,7 +25,8 @@ interface Props { xScale: ScaleTime yScale: ScaleLinear colorScale: (group: string) => string - hoverDatum?: HistogramDatum + hoverData?: HoverData + onHover: (h: HoverData) => void } class HistogramChartBars extends PureComponent { @@ -25,7 +35,13 @@ class HistogramChartBars extends PureComponent { const {key, clip, bars} = group return ( - + { } private get renderData() { - const {data, xScale, yScale, colorScale, hoverDatum} = this.props + const {data, xScale, yScale, colorScale, hoverData} = this.props const {barWidth, sortFn} = this const visibleData = data.filter(d => d.value !== 0) @@ -68,6 +84,12 @@ class HistogramChartBars extends PureComponent { group.sort(sortFn) } + let hoverDataKeys = [] + + if (!!hoverData) { + hoverDataKeys = hoverData.data.map(h => h.key) + } + return groups.map(group => { const time = group[0].time const x = xScale(time) - barWidth / 2 @@ -82,20 +104,17 @@ class HistogramChartBars extends PureComponent { height: yScale(0) - yScale(groupTotal) + BAR_BORDER_RADIUS, }, bars: [], + data: group, } let offset = 0 group.forEach((d: HistogramDatum) => { const height = yScale(0) - yScale(d.value) - - let fill = colorScale(d.group) - - if (!!hoverDatum && hoverDatum.key === d.key) { - fill = color(fill) - .brighter(HOVER_BRIGTHEN_FACTOR) - .hex() - } + const k = hoverDataKeys.includes(d.key) ? HOVER_BRIGTHEN_FACTOR : 0 + const fill = color(colorScale(d.group)) + .brighter(k) + .hex() renderData.bars.push({ key: d.key, @@ -140,6 +159,43 @@ class HistogramChartBars extends PureComponent { return Math.round(width / barCount - BAR_PADDING_SIDES) } + + private handleMouseOver = (e: MouseEvent): void => { + const groupKey = getDeep(e, 'currentTarget.dataset.key', '') + + if (!groupKey) { + return + } + + const {renderData} = this + const hoverGroup = renderData.find(d => d.key === groupKey) + + if (!hoverGroup) { + return + } + + const {data} = hoverGroup + + const barGroup = e.currentTarget as SVGGElement + const boundingRect = barGroup.getBoundingClientRect() + const boundingRectHeight = boundingRect.bottom - boundingRect.top + const y = boundingRect.top + boundingRectHeight / 2 + + let x = boundingRect.right + TOOLTIP_HORIZONTAL_MARGIN + let anchor: TooltipAnchor = 'left' + + // This makes an assumption that the component is within the viewport + if (x >= window.innerWidth - TOOLTIP_REFLECT_DIST) { + x = window.innerWidth - boundingRect.left + TOOLTIP_HORIZONTAL_MARGIN + anchor = 'right' + } + + this.props.onHover({data, x, y, anchor}) + } + + private handleMouseOut = (): void => { + this.props.onHover(null) + } } export default HistogramChartBars diff --git a/ui/src/shared/components/HistogramChartTooltip.tsx b/ui/src/shared/components/HistogramChartTooltip.tsx index c0dc4535ad..c97faa204c 100644 --- a/ui/src/shared/components/HistogramChartTooltip.tsx +++ b/ui/src/shared/components/HistogramChartTooltip.tsx @@ -1,36 +1,43 @@ import React, {SFC, CSSProperties} from 'react' -import {HistogramDatum, TooltipAnchor} from 'src/types/histogram' +import {HoverData, ColorScale} from 'src/types/histogram' interface Props { - datum: HistogramDatum - x: number - y: number - anchor?: TooltipAnchor + data: HoverData + colorScale: ColorScale } const HistogramChartTooltip: SFC = props => { - const {datum, x, y, anchor = 'left'} = props + const {colorScale} = props + const {data, x, y, anchor = 'left'} = props.data - if (!datum) { - return null - } - - const style: CSSProperties = { + const tooltipStyle: CSSProperties = { position: 'fixed', top: y, } if (anchor === 'left') { - style.left = x + tooltipStyle.left = x } else { - style.right = x + tooltipStyle.right = x } return ( -
-
{datum.value}
-
{datum.group}
+
+
+ {data.map(d => ( +
+ {d.value} +
+ ))} +
+
+ {data.map(d => ( +
+ {d.group} +
+ ))} +
) } diff --git a/ui/src/style/components/histogram-chart.scss b/ui/src/style/components/histogram-chart.scss index c253ff85de..242eecb1af 100644 --- a/ui/src/style/components/histogram-chart.scss +++ b/ui/src/style/components/histogram-chart.scss @@ -86,12 +86,12 @@ font-size: 12px; font-weight: 600; color: $g13-mist; - display: flex; - align-items: space-between; transform: translate(0, -50%); pointer-events: none; - - .histogram-chart-tooltip--value { - margin-right: 10px; - } + display: flex; + justify-content: space-between; +} + +.histogram-chart-tooltip--column:first-child { + margin-right: 12px; } diff --git a/ui/src/types/histogram.ts b/ui/src/types/histogram.ts index d5e794fc39..bd33c07a67 100644 --- a/ui/src/types/histogram.ts +++ b/ui/src/types/histogram.ts @@ -22,3 +22,12 @@ export interface Margins { bottom: number left: number } + +export interface HoverData { + data: HistogramData + x: number + y: number + anchor: TooltipAnchor +} + +export type ColorScale = (color: string) => string diff --git a/ui/test/shared/components/HistogramChart.test.tsx b/ui/test/shared/components/HistogramChart.test.tsx index a4637c2d2a..9a25966aa8 100644 --- a/ui/test/shared/components/HistogramChart.test.tsx +++ b/ui/test/shared/components/HistogramChart.test.tsx @@ -13,6 +13,7 @@ describe('HistogramChart', () => { dataStatus: RemoteDataState.Done, width: 600, height: 400, + colorScale: () => 'blue', onZoom: () => {}, } @@ -27,6 +28,7 @@ describe('HistogramChart', () => { dataStatus: RemoteDataState.Done, width: 0, height: 0, + colorScale: () => 'blue', onZoom: () => {}, } @@ -45,6 +47,7 @@ describe('HistogramChart', () => { dataStatus: RemoteDataState.Done, width: 600, height: 400, + colorScale: () => 'blue', onZoom: () => {}, } @@ -63,6 +66,7 @@ describe('HistogramChart', () => { dataStatus: RemoteDataState.Done, width: 600, height: 400, + colorScale: () => 'blue', onZoom: () => {}, } @@ -70,9 +74,6 @@ describe('HistogramChart', () => { const fakeMouseOverEvent = { target: { - dataset: { - key: '0', - }, getBoundingClientRect() { return {top: 10, right: 10, bottom: 5, left: 5} }, @@ -80,7 +81,7 @@ describe('HistogramChart', () => { } wrapper - .find('.histogram-chart') + .find('.histogram-chart-bars--bars') .first() .simulate('mouseover', fakeMouseOverEvent) @@ -95,6 +96,7 @@ describe('HistogramChart', () => { dataStatus: RemoteDataState.Loading, width: 600, height: 400, + colorScale: () => 'blue', onZoom: () => {}, } diff --git a/ui/test/shared/components/__snapshots__/HistogramChart.test.tsx.snap b/ui/test/shared/components/__snapshots__/HistogramChart.test.tsx.snap index 43b5017101..d791611998 100644 --- a/ui/test/shared/components/__snapshots__/HistogramChart.test.tsx.snap +++ b/ui/test/shared/components/__snapshots__/HistogramChart.test.tsx.snap @@ -2,6 +2,7 @@ exports[`HistogramChart displays a HistogramChartSkeleton if empty data is passed 1`] = `
- 0 +
+ 1 +
- a +
+ a +
@@ -111,6 +134,7 @@ exports[`HistogramChart displays a HistogramChartTooltip when hovering over bars exports[`HistogramChart displays a nothing if passed width and height of 0 1`] = ` @@ -307,6 +330,7 @@ exports[`HistogramChart displays the visualization with bars if nonempty data is transform="translate(25, 5)" > - `; From cc1a9670dbeb0b73b447feccbcc214785abdaed1 Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Wed, 27 Jun 2018 16:45:53 -0700 Subject: [PATCH 37/59] Memoize render data in HistogramChartBars --- .../shared/components/HistogramChartBars.tsx | 198 ++++++++++-------- 1 file changed, 108 insertions(+), 90 deletions(-) diff --git a/ui/src/shared/components/HistogramChartBars.tsx b/ui/src/shared/components/HistogramChartBars.tsx index 8b7ecfe4cf..d8f13de9d0 100644 --- a/ui/src/shared/components/HistogramChartBars.tsx +++ b/ui/src/shared/components/HistogramChartBars.tsx @@ -18,6 +18,95 @@ const HOVER_BRIGTHEN_FACTOR = 0.4 const TOOLTIP_HORIZONTAL_MARGIN = 5 const TOOLTIP_REFLECT_DIST = 100 +const getBarWidth = ({data, xScale, width}) => { + const dataInView = data.filter( + d => xScale(d.time) >= 0 && xScale(d.time) <= width + ) + const barCount = Object.values(_.groupBy(dataInView, 'time')).length + + return Math.round(width / barCount - BAR_PADDING_SIDES) +} + +const getSortFn = data => { + const counts = {} + + for (const d of data) { + if (counts[d.group]) { + counts[d.group] += d.value + } else { + counts[d.group] = d.value + } + } + + return (a, b) => counts[b.group] - counts[a.group] +} + +const getRenderData = ({ + data, + width, + xScale, + yScale, + colorScale, + hoverData, +}) => { + const barWidth = getBarWidth({data, xScale, width}) + const sortFn = getSortFn(data) + const visibleData = data.filter(d => d.value !== 0) + const groups = Object.values(_.groupBy(visibleData, 'time')) + + for (const group of groups) { + group.sort(sortFn) + } + + let hoverDataKeys = [] + + if (!!hoverData) { + hoverDataKeys = hoverData.data.map(h => h.key) + } + + return groups.map(group => { + const time = group[0].time + const x = xScale(time) - barWidth / 2 + const groupTotal = _.sumBy(group, 'value') + + const renderData = { + key: `${time}-${groupTotal}-${x}`, + clip: { + x, + y: yScale(groupTotal), + width: barWidth, + height: yScale(0) - yScale(groupTotal) + BAR_BORDER_RADIUS, + }, + bars: [], + data: group, + } + + let offset = 0 + + group.forEach((d: HistogramDatum) => { + const height = yScale(0) - yScale(d.value) + const k = hoverDataKeys.includes(d.key) ? HOVER_BRIGTHEN_FACTOR : 0 + const fill = color(colorScale(d.group)) + .brighter(k) + .hex() + + renderData.bars.push({ + key: d.key, + group: d.group, + x, + y: yScale(d.value) - offset, + width: barWidth, + height, + fill, + }) + + offset += height + }) + + return renderData + }) +} + interface Props { width: number height: number @@ -29,9 +118,25 @@ interface Props { onHover: (h: HoverData) => void } -class HistogramChartBars extends PureComponent { +interface State { + renderData: any[] +} + +class HistogramChartBars extends PureComponent { + public static getDerivedStateFromProps(props) { + return {renderData: getRenderData(props)} + } + + constructor(props) { + super(props) + + this.state = {renderData: []} + } + public render() { - return this.renderData.map(group => { + const {renderData} = this.state + + return renderData.map(group => { const {key, clip, bars} = group return ( @@ -73,93 +178,6 @@ class HistogramChartBars extends PureComponent { }) } - private get renderData() { - const {data, xScale, yScale, colorScale, hoverData} = this.props - const {barWidth, sortFn} = this - - const visibleData = data.filter(d => d.value !== 0) - const groups = Object.values(_.groupBy(visibleData, 'time')) - - for (const group of groups) { - group.sort(sortFn) - } - - let hoverDataKeys = [] - - if (!!hoverData) { - hoverDataKeys = hoverData.data.map(h => h.key) - } - - return groups.map(group => { - const time = group[0].time - const x = xScale(time) - barWidth / 2 - const groupTotal = _.sumBy(group, 'value') - - const renderData = { - key: `${time}-${groupTotal}-${x}`, - clip: { - x, - y: yScale(groupTotal), - width: barWidth, - height: yScale(0) - yScale(groupTotal) + BAR_BORDER_RADIUS, - }, - bars: [], - data: group, - } - - let offset = 0 - - group.forEach((d: HistogramDatum) => { - const height = yScale(0) - yScale(d.value) - const k = hoverDataKeys.includes(d.key) ? HOVER_BRIGTHEN_FACTOR : 0 - const fill = color(colorScale(d.group)) - .brighter(k) - .hex() - - renderData.bars.push({ - key: d.key, - group: d.group, - x, - y: yScale(d.value) - offset, - width: barWidth, - height, - fill, - }) - - offset += height - }) - - return renderData - }) - } - - private get sortFn() { - const {data} = this.props - - const counts = {} - - for (const d of data) { - if (counts[d.group]) { - counts[d.group] += d.value - } else { - counts[d.group] = d.value - } - } - - return (a, b) => counts[b.group] - counts[a.group] - } - - private get barWidth() { - const {data, xScale, width} = this.props - - const dataInView = data.filter( - d => xScale(d.time) >= 0 && xScale(d.time) <= width - ) - const barCount = Object.values(_.groupBy(dataInView, 'time')).length - - return Math.round(width / barCount - BAR_PADDING_SIDES) - } - private handleMouseOver = (e: MouseEvent): void => { const groupKey = getDeep(e, 'currentTarget.dataset.key', '') @@ -167,7 +185,7 @@ class HistogramChartBars extends PureComponent { return } - const {renderData} = this + const {renderData} = this.state const hoverGroup = renderData.find(d => d.key === groupKey) if (!hoverGroup) { From 3e55d1bc7d0ddedcc27b266a78ea9688922113d5 Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Wed, 27 Jun 2018 16:48:51 -0700 Subject: [PATCH 38/59] Improve HistogramChartBars typing --- .../shared/components/HistogramChartBars.tsx | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/ui/src/shared/components/HistogramChartBars.tsx b/ui/src/shared/components/HistogramChartBars.tsx index d8f13de9d0..69368618b2 100644 --- a/ui/src/shared/components/HistogramChartBars.tsx +++ b/ui/src/shared/components/HistogramChartBars.tsx @@ -18,7 +18,7 @@ const HOVER_BRIGTHEN_FACTOR = 0.4 const TOOLTIP_HORIZONTAL_MARGIN = 5 const TOOLTIP_REFLECT_DIST = 100 -const getBarWidth = ({data, xScale, width}) => { +const getBarWidth = ({data, xScale, width}): number => { const dataInView = data.filter( d => xScale(d.time) >= 0 && xScale(d.time) <= width ) @@ -27,7 +27,9 @@ const getBarWidth = ({data, xScale, width}) => { return Math.round(width / barCount - BAR_PADDING_SIDES) } -const getSortFn = data => { +type SortFn = (a: HistogramDatum, b: HistogramDatum) => number + +const getSortFn = (data: HistogramData): SortFn => { const counts = {} for (const d of data) { @@ -41,6 +43,30 @@ const getSortFn = data => { return (a, b) => counts[b.group] - counts[a.group] } +interface RenderDataBar { + key: string + group: string + x: number + y: number + width: number + height: number + fill: string +} + +interface RenderDataGroup { + key: string + clip: { + x: number + y: number + width: number + height: number + } + bars: RenderDataBar[] + data: HistogramData +} + +type RenderData = RenderDataGroup[] + const getRenderData = ({ data, width, @@ -48,7 +74,7 @@ const getRenderData = ({ yScale, colorScale, hoverData, -}) => { +}): RenderData => { const barWidth = getBarWidth({data, xScale, width}) const sortFn = getSortFn(data) const visibleData = data.filter(d => d.value !== 0) @@ -119,7 +145,7 @@ interface Props { } interface State { - renderData: any[] + renderData: RenderData } class HistogramChartBars extends PureComponent { From 03278c6c4a993b5fa097e67a3024db7b7ea7a05a Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Wed, 27 Jun 2018 16:58:07 -0700 Subject: [PATCH 39/59] Use ColorScale type consistently --- ui/src/shared/components/HistogramChart.tsx | 3 ++- ui/src/shared/components/HistogramChartBars.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ui/src/shared/components/HistogramChart.tsx b/ui/src/shared/components/HistogramChart.tsx index 820dc79ecc..68e2b8f69a 100644 --- a/ui/src/shared/components/HistogramChart.tsx +++ b/ui/src/shared/components/HistogramChart.tsx @@ -16,6 +16,7 @@ import { HistogramData, Margins, HoverData, + ColorScale, } from 'src/types/histogram' const PADDING_TOP = 0.2 @@ -31,7 +32,7 @@ interface Props { dataStatus: RemoteDataState width: number height: number - colorScale: (group: string) => string + colorScale: ColorScale onZoom: (TimePeriod) => void } diff --git a/ui/src/shared/components/HistogramChartBars.tsx b/ui/src/shared/components/HistogramChartBars.tsx index 69368618b2..0df3edb5e8 100644 --- a/ui/src/shared/components/HistogramChartBars.tsx +++ b/ui/src/shared/components/HistogramChartBars.tsx @@ -10,6 +10,7 @@ import { HistogramDatum, HoverData, TooltipAnchor, + ColorScale, } from 'src/types/histogram' const BAR_BORDER_RADIUS = 4 @@ -139,7 +140,7 @@ interface Props { data: HistogramData xScale: ScaleTime yScale: ScaleLinear - colorScale: (group: string) => string + colorScale: ColorScale hoverData?: HoverData onHover: (h: HoverData) => void } From c623808c69f25bd65297ae18a35d5caec287a129 Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Thu, 28 Jun 2018 09:55:46 -0700 Subject: [PATCH 40/59] Improve naming in HistogramChartBars --- .../shared/components/HistogramChartBars.tsx | 71 +++++++++---------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/ui/src/shared/components/HistogramChartBars.tsx b/ui/src/shared/components/HistogramChartBars.tsx index 0df3edb5e8..16967e3aa5 100644 --- a/ui/src/shared/components/HistogramChartBars.tsx +++ b/ui/src/shared/components/HistogramChartBars.tsx @@ -44,17 +44,7 @@ const getSortFn = (data: HistogramData): SortFn => { return (a, b) => counts[b.group] - counts[a.group] } -interface RenderDataBar { - key: string - group: string - x: number - y: number - width: number - height: number - fill: string -} - -interface RenderDataGroup { +interface BarGroup { key: string clip: { x: number @@ -62,27 +52,33 @@ interface RenderDataGroup { width: number height: number } - bars: RenderDataBar[] + bars: Array<{ + key: string + group: string + x: number + y: number + width: number + height: number + fill: string + }> data: HistogramData } -type RenderData = RenderDataGroup[] - -const getRenderData = ({ +const getBarGroups = ({ data, width, xScale, yScale, colorScale, hoverData, -}): RenderData => { +}): BarGroup[] => { const barWidth = getBarWidth({data, xScale, width}) const sortFn = getSortFn(data) const visibleData = data.filter(d => d.value !== 0) - const groups = Object.values(_.groupBy(visibleData, 'time')) + const timeGroups = Object.values(_.groupBy(visibleData, 'time')) - for (const group of groups) { - group.sort(sortFn) + for (const timeGroup of timeGroups) { + timeGroup.sort(sortFn) } let hoverDataKeys = [] @@ -91,33 +87,33 @@ const getRenderData = ({ hoverDataKeys = hoverData.data.map(h => h.key) } - return groups.map(group => { - const time = group[0].time + return timeGroups.map(timeGroup => { + const time = timeGroup[0].time const x = xScale(time) - barWidth / 2 - const groupTotal = _.sumBy(group, 'value') + const total = _.sumBy(timeGroup, 'value') - const renderData = { - key: `${time}-${groupTotal}-${x}`, + const barGroup = { + key: `${time}-${total}-${x}`, clip: { x, - y: yScale(groupTotal), + y: yScale(total), width: barWidth, - height: yScale(0) - yScale(groupTotal) + BAR_BORDER_RADIUS, + height: yScale(0) - yScale(total) + BAR_BORDER_RADIUS, }, bars: [], - data: group, + data: timeGroup, } let offset = 0 - group.forEach((d: HistogramDatum) => { + timeGroup.forEach((d: HistogramDatum) => { const height = yScale(0) - yScale(d.value) const k = hoverDataKeys.includes(d.key) ? HOVER_BRIGTHEN_FACTOR : 0 const fill = color(colorScale(d.group)) .brighter(k) .hex() - renderData.bars.push({ + barGroup.bars.push({ key: d.key, group: d.group, x, @@ -130,7 +126,7 @@ const getRenderData = ({ offset += height }) - return renderData + return barGroup }) } @@ -146,24 +142,24 @@ interface Props { } interface State { - renderData: RenderData + barGroups: BarGroup[] } class HistogramChartBars extends PureComponent { public static getDerivedStateFromProps(props) { - return {renderData: getRenderData(props)} + return {barGroups: getBarGroups(props)} } constructor(props) { super(props) - this.state = {renderData: []} + this.state = {barGroups: []} } public render() { - const {renderData} = this.state + const {barGroups} = this.state - return renderData.map(group => { + return barGroups.map(group => { const {key, clip, bars} = group return ( @@ -212,15 +208,14 @@ class HistogramChartBars extends PureComponent { return } - const {renderData} = this.state - const hoverGroup = renderData.find(d => d.key === groupKey) + const {barGroups} = this.state + const hoverGroup = barGroups.find(d => d.key === groupKey) if (!hoverGroup) { return } const {data} = hoverGroup - const barGroup = e.currentTarget as SVGGElement const boundingRect = barGroup.getBoundingClientRect() const boundingRectHeight = boundingRect.bottom - boundingRect.top From 82588880f2521a3ccb46c396154b30212a8bed20 Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Thu, 28 Jun 2018 11:49:33 -0700 Subject: [PATCH 41/59] Use approriate text baseline property --- ui/src/style/components/histogram-chart.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/style/components/histogram-chart.scss b/ui/src/style/components/histogram-chart.scss index 242eecb1af..44a52126d3 100644 --- a/ui/src/style/components/histogram-chart.scss +++ b/ui/src/style/components/histogram-chart.scss @@ -48,12 +48,12 @@ .x-label { text-anchor: middle; - alignment-baseline: hanging; + dominant-baseline: hanging; } .y-label { text-anchor: end; - alignment-baseline: middle; + dominant-baseline: middle; } .y-tick { From 6f4891c091a0fa53906dfec336b91ce60aa18480 Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Thu, 28 Jun 2018 10:48:27 -0700 Subject: [PATCH 42/59] Remove logs histogramDataStatus state --- ui/src/logs/actions/index.ts | 35 +++----------------- ui/src/logs/containers/LogsPage.tsx | 8 ++--- ui/src/logs/reducers/index.ts | 4 --- ui/src/shared/components/HistogramChart.tsx | 23 ++----------- ui/src/style/components/histogram-chart.scss | 33 +----------------- ui/src/types/logs.ts | 2 -- 6 files changed, 9 insertions(+), 96 deletions(-) diff --git a/ui/src/logs/actions/index.ts b/ui/src/logs/actions/index.ts index 928ecf9aa6..be03b8244a 100644 --- a/ui/src/logs/actions/index.ts +++ b/ui/src/logs/actions/index.ts @@ -1,12 +1,6 @@ import moment from 'moment' import _ from 'lodash' -import { - Source, - Namespace, - TimeRange, - QueryConfig, - RemoteDataState, -} from 'src/types' +import {Source, Namespace, TimeRange, QueryConfig} from 'src/types' import {getSource} from 'src/shared/apis' import {getDatabasesWithRetentionPolicies} from 'src/shared/apis/databases' import { @@ -47,7 +41,6 @@ export enum ActionTypes { SetNamespace = 'LOGS_SET_NAMESPACE', SetHistogramQueryConfig = 'LOGS_SET_HISTOGRAM_QUERY_CONFIG', SetHistogramData = 'LOGS_SET_HISTOGRAM_DATA', - SetHistogramDataStatus = 'LOGS_SET_HISTOGRAM_DATA_STATUS', SetTableQueryConfig = 'LOGS_SET_TABLE_QUERY_CONFIG', SetTableData = 'LOGS_SET_TABLE_DATA', ChangeZoom = 'LOGS_CHANGE_ZOOM', @@ -139,11 +132,6 @@ interface SetHistogramData { } } -interface SetHistogramDataStatus { - type: ActionTypes.SetHistogramDataStatus - payload: RemoteDataState -} - interface SetTableQueryConfig { type: ActionTypes.SetTableQueryConfig payload: { @@ -179,7 +167,6 @@ export type Action = | SetNamespaceAction | SetHistogramQueryConfig | SetHistogramData - | SetHistogramDataStatus | ChangeZoomAction | SetTableData | SetTableQueryConfig @@ -237,13 +224,6 @@ const setHistogramData = (data): SetHistogramData => ({ payload: {data}, }) -const setHistogramDataStatus = ( - status: RemoteDataState -): SetHistogramDataStatus => ({ - type: ActionTypes.SetHistogramDataStatus, - payload: status, -}) - export const executeHistogramQueryAsync = () => async ( dispatch, getState: GetState @@ -260,17 +240,10 @@ export const executeHistogramQueryAsync = () => async ( if (_.every([queryConfig, timeRange, namespace, proxyLink])) { const query = buildLogQuery(timeRange, queryConfig, filters, searchTerm) - try { - dispatch(setHistogramDataStatus(RemoteDataState.Loading)) + const response = await executeQueryAsync(proxyLink, namespace, query) + const data = parseHistogramQueryResponse(response) - const response = await executeQueryAsync(proxyLink, namespace, query) - const data = parseHistogramQueryResponse(response) - - dispatch(setHistogramData(data)) - dispatch(setHistogramDataStatus(RemoteDataState.Done)) - } catch { - dispatch(setHistogramDataStatus(RemoteDataState.Error)) - } + dispatch(setHistogramData(data)) } } diff --git a/ui/src/logs/containers/LogsPage.tsx b/ui/src/logs/containers/LogsPage.tsx index 868f6766b4..c1b9570a96 100644 --- a/ui/src/logs/containers/LogsPage.tsx +++ b/ui/src/logs/containers/LogsPage.tsx @@ -26,7 +26,7 @@ import LogsTable from 'src/logs/components/LogsTable' import {getDeep} from 'src/utils/wrappers' import {colorForSeverity} from 'src/logs/utils/colors' -import {Source, Namespace, TimeRange, RemoteDataState} from 'src/types' +import {Source, Namespace, TimeRange} from 'src/types' import {Filter} from 'src/types/logs' import {HistogramData, TimePeriod} from 'src/types/histogram' @@ -48,7 +48,6 @@ interface Props { changeFilter: (id: string, operator: string, value: string) => void timeRange: TimeRange histogramData: HistogramData - histogramDataStatus: RemoteDataState tableData: { columns: string[] values: string[] @@ -180,14 +179,13 @@ class LogsPage extends PureComponent { } private get chart(): JSX.Element { - const {histogramData, histogramDataStatus} = this.props + const {histogramData} = this.props return ( {({width, height}) => ( { return {...state, histogramQueryConfig: action.payload.queryConfig} case ActionTypes.SetHistogramData: return {...state, histogramData: action.payload.data} - case ActionTypes.SetHistogramDataStatus: - return {...state, histogramDataStatus: action.payload} case ActionTypes.SetTableQueryConfig: return {...state, tableQueryConfig: action.payload.queryConfig} case ActionTypes.SetTableData: diff --git a/ui/src/shared/components/HistogramChart.tsx b/ui/src/shared/components/HistogramChart.tsx index 68e2b8f69a..b2902dde1a 100644 --- a/ui/src/shared/components/HistogramChart.tsx +++ b/ui/src/shared/components/HistogramChart.tsx @@ -10,7 +10,6 @@ import XBrush from 'src/shared/components/XBrush' import extentBy from 'src/utils/extentBy' -import {RemoteDataState} from 'src/types' import { TimePeriod, HistogramData, @@ -29,7 +28,6 @@ const PERIOD_DIGIT_WIDTH = 4 interface Props { data: HistogramData - dataStatus: RemoteDataState width: number height: number colorScale: ColorScale @@ -66,22 +64,11 @@ class HistogramChart extends PureComponent { } const {hoverData} = this.state - const { - xScale, - yScale, - adjustedWidth, - adjustedHeight, - bodyTransform, - loadingClass, - } = this + const {xScale, yScale, adjustedWidth, adjustedHeight, bodyTransform} = this return ( <> - + @@ -189,12 +176,6 @@ class HistogramChart extends PureComponent { return Math.max(...counts) } - private get loadingClass(): string { - const {dataStatus} = this.props - - return dataStatus === RemoteDataState.Loading ? 'loading' : '' - } - private handleBrush = (t: TimePeriod): void => { this.props.onZoom(t) this.setState({hoverData: null}) diff --git a/ui/src/style/components/histogram-chart.scss b/ui/src/style/components/histogram-chart.scss index 44a52126d3..9eb5a218f3 100644 --- a/ui/src/style/components/histogram-chart.scss +++ b/ui/src/style/components/histogram-chart.scss @@ -1,36 +1,5 @@ -@keyframes blur-in { - from { - filter: blur(0); - } - - to { - filter: blur(2px); - } -} - -@keyframes blur-out { - from { - filter: blur(2px); - } - - to { - filter: blur(0); - } -} - .histogram-chart { user-select: none; - - &:not(.loading) { - animation-duration: 0.1s; - animation-name: blur-out; - } - - &.loading { - animation-duration: 0.3s; - animation-name: blur-in; - animation-fill-mode: forwards; - } } .histogram-chart-bars--bar { @@ -64,7 +33,7 @@ } -.histogram-chart-skeleton, .histogram-chart:not(.loading) .x-brush--area { +.histogram-chart-skeleton, .histogram-chart .x-brush--area { cursor: crosshair; } diff --git a/ui/src/types/logs.ts b/ui/src/types/logs.ts index 975af52487..d0e14d8b3b 100644 --- a/ui/src/types/logs.ts +++ b/ui/src/types/logs.ts @@ -3,7 +3,6 @@ import { TimeRange, Namespace, Source, - RemoteDataState, } from 'src/types' export interface Filter { @@ -25,7 +24,6 @@ export interface LogsState { timeRange: TimeRange histogramQueryConfig: QueryConfig | null histogramData: object[] - histogramDataStatus: RemoteDataState tableQueryConfig: QueryConfig | null tableData: TableData searchTerm: string | null From eadd53053b8b08d4feb4baf5c69133ac19786de4 Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Thu, 28 Jun 2018 11:09:08 -0700 Subject: [PATCH 43/59] Increment logs queryCount state for all queries --- ui/src/logs/actions/index.ts | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/ui/src/logs/actions/index.ts b/ui/src/logs/actions/index.ts index be03b8244a..a099ba22b2 100644 --- a/ui/src/logs/actions/index.ts +++ b/ui/src/logs/actions/index.ts @@ -237,13 +237,20 @@ export const executeHistogramQueryAsync = () => async ( const searchTerm = getSearchTerm(state) const filters = getFilters(state) - if (_.every([queryConfig, timeRange, namespace, proxyLink])) { - const query = buildLogQuery(timeRange, queryConfig, filters, searchTerm) + if (!_.every([queryConfig, timeRange, namespace, proxyLink])) { + return + } + try { + dispatch(incrementQueryCount()) + + const query = buildLogQuery(timeRange, queryConfig, filters, searchTerm) const response = await executeQueryAsync(proxyLink, namespace, query) const data = parseHistogramQueryResponse(response) dispatch(setHistogramData(data)) + } finally { + dispatch(decrementQueryCount()) } } @@ -265,7 +272,13 @@ export const executeTableQueryAsync = () => async ( const searchTerm = getSearchTerm(state) const filters = getFilters(state) - if (_.every([queryConfig, timeRange, namespace, proxyLink])) { + if (!_.every([queryConfig, timeRange, namespace, proxyLink])) { + return + } + + try { + dispatch(incrementQueryCount()) + const query = buildLogQuery(timeRange, queryConfig, filters, searchTerm) const response = await executeQueryAsync( proxyLink, @@ -276,6 +289,8 @@ export const executeTableQueryAsync = () => async ( const series = getDeep(response, 'results.0.series.0', defaultTableData) dispatch(setTableData(series)) + } finally { + dispatch(decrementQueryCount()) } } @@ -288,16 +303,13 @@ export const incrementQueryCount = () => ({ }) export const executeQueriesAsync = () => async dispatch => { - dispatch(incrementQueryCount()) try { await Promise.all([ dispatch(executeHistogramQueryAsync()), dispatch(executeTableQueryAsync()), ]) - } catch (ex) { + } catch { console.error('Could not make query requests') - } finally { - dispatch(decrementQueryCount()) } } From 5dd96d6b6b883e90f76130995998737947007419 Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Thu, 28 Jun 2018 11:09:37 -0700 Subject: [PATCH 44/59] Remove duplicate log table query --- ui/src/logs/actions/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/src/logs/actions/index.ts b/ui/src/logs/actions/index.ts index a099ba22b2..26f4f6da26 100644 --- a/ui/src/logs/actions/index.ts +++ b/ui/src/logs/actions/index.ts @@ -483,6 +483,5 @@ export const changeZoomAsync = (timeRange: TimeRange) => async ( if (namespace && proxyLink) { await dispatch(setTimeRangeAsync(timeRange)) - await dispatch(executeTableQueryAsync()) } } From 1d8d294f05c39b93a763ea7433be98861162ebff Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Thu, 28 Jun 2018 11:37:48 -0700 Subject: [PATCH 45/59] Remove HistogramChart loading test --- .../shared/components/HistogramChart.test.tsx | 17 +---------------- .../__snapshots__/HistogramChart.test.tsx.snap | 2 +- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/ui/test/shared/components/HistogramChart.test.tsx b/ui/test/shared/components/HistogramChart.test.tsx index 9a25966aa8..06a6835750 100644 --- a/ui/test/shared/components/HistogramChart.test.tsx +++ b/ui/test/shared/components/HistogramChart.test.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {mount, shallow} from 'enzyme' +import {mount} from 'enzyme' import HistogramChart from 'src/shared/components/HistogramChart' import HistogramChartTooltip from 'src/shared/components/HistogramChartTooltip' @@ -89,19 +89,4 @@ describe('HistogramChart', () => { expect(tooltip).toMatchSnapshot() }) - - test('has a "loading" class if data is reloading', () => { - const props = { - data: [{key: '', time: 0, value: 0, group: ''}], - dataStatus: RemoteDataState.Loading, - width: 600, - height: 400, - colorScale: () => 'blue', - onZoom: () => {}, - } - - const wrapper = shallow() - - expect(wrapper.find('.histogram-chart').hasClass('loading')).toBe(true) - }) }) diff --git a/ui/test/shared/components/__snapshots__/HistogramChart.test.tsx.snap b/ui/test/shared/components/__snapshots__/HistogramChart.test.tsx.snap index d791611998..21c90d3427 100644 --- a/ui/test/shared/components/__snapshots__/HistogramChart.test.tsx.snap +++ b/ui/test/shared/components/__snapshots__/HistogramChart.test.tsx.snap @@ -174,7 +174,7 @@ exports[`HistogramChart displays the visualization with bars if nonempty data is width={600} > From 701b234542657c234f30e42072ca2a55b4709a72 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 28 Jun 2018 13:37:09 -0700 Subject: [PATCH 46/59] Remove magic numbers --- ui/src/dashboards/components/CellEditorOverlay.tsx | 6 ++++-- ui/src/shared/apis/query.ts | 3 ++- ui/src/shared/constants/index.tsx | 6 ++++-- ui/src/tempVars/utils/replace.ts | 10 +++++++--- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.tsx b/ui/src/dashboards/components/CellEditorOverlay.tsx index fe64060529..b1757476eb 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.tsx +++ b/ui/src/dashboards/components/CellEditorOverlay.tsx @@ -32,6 +32,8 @@ import { AUTO_GROUP_BY, PREDEFINED_TEMP_VARS, TEMP_VAR_DASHBOARD_TIME, + DEFAULT_DURATION_MS, + DEFAULT_PIXELS, } from 'src/shared/constants' import {getCellTypeColors} from 'src/dashboards/constants/cellEditor' @@ -405,10 +407,10 @@ class CellEditorOverlay extends Component { // get durationMs to calculate interval let queries = await getQueryConfigAndStatus(url, [{query, id}]) - const durationMs = _.get(queries, '0.durationMs', 1000) + const durationMs = _.get(queries, '0.durationMs', DEFAULT_DURATION_MS) // calc and replace :interval: - query = replaceInterval(query, 333, durationMs) + query = replaceInterval(query, DEFAULT_PIXELS, durationMs) // fetch queryConfig for with all template variables replaced queries = await getQueryConfigAndStatus(url, [{query, id}]) diff --git a/ui/src/shared/apis/query.ts b/ui/src/shared/apis/query.ts index 4cac8786d4..ba064008ec 100644 --- a/ui/src/shared/apis/query.ts +++ b/ui/src/shared/apis/query.ts @@ -2,6 +2,7 @@ import _ from 'lodash' import {getDeep} from 'src/utils/wrappers' import {fetchTimeSeriesAsync} from 'src/shared/actions/timeSeries' import {analyzeQueries} from 'src/shared/apis' +import {DEFAULT_DURATION_MS} from 'src/shared/constants' import replaceTemplates, {replaceInterval} from 'src/tempVars/utils/replace' import {Source} from 'src/types' @@ -65,7 +66,7 @@ const replace = async ( const duration = async (query: string, source: Source): Promise => { try { const analysis = await analyzeQueries(source.links.queries, [{query}]) - return getDeep(analysis, '0.durationMs', 1000) + return getDeep(analysis, '0.durationMs', DEFAULT_DURATION_MS) } catch (error) { console.error(error) throw error diff --git a/ui/src/shared/constants/index.tsx b/ui/src/shared/constants/index.tsx index 10f1931b91..9ad1c00f19 100644 --- a/ui/src/shared/constants/index.tsx +++ b/ui/src/shared/constants/index.tsx @@ -3,6 +3,9 @@ import _ from 'lodash' import {TemplateValueType, TemplateType} from 'src/types' import {CellType} from 'src/types/dashboards' +export const DEFAULT_DURATION_MS = 1000 +export const DEFAULT_PIXELS = 333 + export const NO_CELL = 'none' export const PERMISSIONS = { @@ -439,9 +442,8 @@ export const DEFAULT_SOURCE = { metaUrl: '', } -export const defaultIntervalValue = '333' export const intervalValuesPoints = [ - {value: defaultIntervalValue, type: TemplateValueType.Points, selected: true}, + {value: `${DEFAULT_PIXELS}`, type: TemplateValueType.Points, selected: true}, ] export const interval = { diff --git a/ui/src/tempVars/utils/replace.ts b/ui/src/tempVars/utils/replace.ts index 304d368cf1..b85b0142a6 100644 --- a/ui/src/tempVars/utils/replace.ts +++ b/ui/src/tempVars/utils/replace.ts @@ -1,5 +1,9 @@ import {Template, TemplateValueType, TemplateValue} from 'src/types/tempVars' -import {TEMP_VAR_INTERVAL} from 'src/shared/constants' +import { + TEMP_VAR_INTERVAL, + DEFAULT_PIXELS, + DEFAULT_DURATION_MS, +} from 'src/shared/constants' export const replaceInterval = ( query: string, @@ -11,11 +15,11 @@ export const replaceInterval = ( } if (!pixels) { - pixels = 333 + pixels = DEFAULT_PIXELS } if (!durationMs) { - durationMs = 1000 + durationMs = DEFAULT_DURATION_MS } // duration / width of visualization in pixels From 4a5cef976ceae002a19439deb353efeed4b44e44 Mon Sep 17 00:00:00 2001 From: Alirie Gray Date: Thu, 28 Jun 2018 13:41:12 -0700 Subject: [PATCH 47/59] Add position property to LogViewerUIColumn Swagger definition --- server/swagger.json | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/server/swagger.json b/server/swagger.json index 381a7f316e..bb591d4927 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -5148,13 +5148,18 @@ "type": "object", "required": [ "name", - "encoding" + "encoding", + "position" ], "properties": { "name": { "description": "Unique identifier name of the column", "type": "string" }, + "position": { + "type": "integer", + "format": "int32" + }, "encoding": { "description": "Composable encoding options for the column", "type": "array", @@ -5177,6 +5182,7 @@ }, "example": { "name": "severity", + "position": 0, "encoding": [ { "type": "label", @@ -5224,6 +5230,7 @@ "columns": [ { "name": "severity", + "position": 0, "encoding": [ { "type": "label", @@ -5255,11 +5262,13 @@ }, { "name": "messages", - "encoding": [] - }, - { - "name": "timestamp", - "encoding": [] + "position": 1, + "encoding": [ + { + "type": "visibility", + "value": "hidden" + } + ] } ] } From c912e56b0268c6f5297d02f07519aff895658df7 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 28 Jun 2018 13:44:49 -0700 Subject: [PATCH 48/59] Fix spelling errors --- ui/src/tempVars/utils/replace.ts | 2 +- ui/test/tempVars/utils/replace.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/tempVars/utils/replace.ts b/ui/src/tempVars/utils/replace.ts index b85b0142a6..870f2f87ac 100644 --- a/ui/src/tempVars/utils/replace.ts +++ b/ui/src/tempVars/utils/replace.ts @@ -94,7 +94,7 @@ const replaceAllRegex = ( search: string, replacement: string ) => { - // check for presence of anythine between two forward slashes /[your stuff here]/ + // check for presence of anything between two forward slashes /[your stuff here]/ const matches = query.match(/\/([^\/]*)\//gm) if (!matches) { diff --git a/ui/test/tempVars/utils/replace.test.ts b/ui/test/tempVars/utils/replace.test.ts index 1441384c18..24f7fe28c4 100644 --- a/ui/test/tempVars/utils/replace.test.ts +++ b/ui/test/tempVars/utils/replace.test.ts @@ -38,7 +38,7 @@ describe('templates.utils.replace', () => { expect(actual).toBe(expected) }) - it('can replace all in a select with paramaters and aggregates', () => { + it('can replace all in a select with parameters and aggregates', () => { const vars = [ { ...emptyTemplate, From 00d5d7d09b91e7283ef78b25cac64283b9f2234b Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 28 Jun 2018 14:19:57 -0700 Subject: [PATCH 49/59] Always supply the rawText to children --- .../components/CellEditorOverlay.tsx | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/ui/src/dashboards/components/CellEditorOverlay.tsx b/ui/src/dashboards/components/CellEditorOverlay.tsx index b1757476eb..dbe3ac1bdf 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.tsx +++ b/ui/src/dashboards/components/CellEditorOverlay.tsx @@ -404,16 +404,29 @@ class CellEditorOverlay extends Component { ): Promise => { // replace all templates but :interval: query = replaceTemplate(query, templates) + let queries = [] + let durationMs = DEFAULT_DURATION_MS - // get durationMs to calculate interval - let queries = await getQueryConfigAndStatus(url, [{query, id}]) - const durationMs = _.get(queries, '0.durationMs', DEFAULT_DURATION_MS) + try { + // get durationMs to calculate interval + queries = await getQueryConfigAndStatus(url, [{query, id}]) + durationMs = _.get(queries, '0.durationMs', DEFAULT_DURATION_MS) - // calc and replace :interval: - query = replaceInterval(query, DEFAULT_PIXELS, durationMs) + // calc and replace :interval: + query = replaceInterval(query, DEFAULT_PIXELS, durationMs) + } catch (error) { + console.error(error) + throw error + } + + try { + // fetch queryConfig for with all template variables replaced + queries = await getQueryConfigAndStatus(url, [{query, id}]) + } catch (error) { + console.error(error) + throw error + } - // fetch queryConfig for with all template variables replaced - queries = await getQueryConfigAndStatus(url, [{query, id}]) const {queryConfig} = queries.find(q => q.id === id) return queryConfig @@ -438,7 +451,6 @@ class CellEditorOverlay extends Component { try { const queryConfig = await this.getConfig(url, id, text, templates) - const nextQueries = this.state.queriesWorkingDraft.map(q => { if (q.id === id) { const isQuerySupportedByExplorer = !isUsingUserDefinedTempVars @@ -449,6 +461,7 @@ class CellEditorOverlay extends Component { return { ...queryConfig, + rawText: text, source: q.source, isQuerySupportedByExplorer, } From c47cb386a969ef2515a0ceb92d4de5580e6885f3 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 28 Jun 2018 15:09:12 -0700 Subject: [PATCH 50/59] Remove resolution from query struct --- chronograf.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/chronograf.go b/chronograf.go index e58cd199ba..7e5b43f0ed 100644 --- a/chronograf.go +++ b/chronograf.go @@ -184,15 +184,14 @@ type Template struct { // Query retrieves a Response from a TimeSeries. type Query struct { - Command string `json:"query"` // Command is the query itself - DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. - RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. - Epoch string `json:"epoch,omitempty"` // Epoch is the time format for the return results - Wheres []string `json:"wheres,omitempty"` // Wheres restricts the query to certain attributes - GroupBys []string `json:"groupbys,omitempty"` // GroupBys collate the query by these tags - Resolution uint `json:"resolution,omitempty"` // Resolution is the available screen resolution to render query results - Label string `json:"label,omitempty"` // Label is the Y-Axis label for the data - Range *Range `json:"range,omitempty"` // Range is the default Y-Axis range for the data + Command string `json:"query"` // Command is the query itself + DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. + RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. + Epoch string `json:"epoch,omitempty"` // Epoch is the time format for the return results + Wheres []string `json:"wheres,omitempty"` // Wheres restricts the query to certain attributes + GroupBys []string `json:"groupbys,omitempty"` // GroupBys collate the query by these tags + Label string `json:"label,omitempty"` // Label is the Y-Axis label for the data + Range *Range `json:"range,omitempty"` // Range is the default Y-Axis range for the data } // DashboardQuery includes state for the query builder. This is a transition From 6a738c683ababfbb505da092dfec3156528775df Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 28 Jun 2018 15:09:23 -0700 Subject: [PATCH 51/59] Fix spelling --- chronograf.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chronograf.go b/chronograf.go index 7e5b43f0ed..8a1c54f47d 100644 --- a/chronograf.go +++ b/chronograf.go @@ -210,7 +210,7 @@ type TemplateQuery struct { Command string `json:"influxql"` // Command is the query itself DB string `json:"db,omitempty"` // DB is optional and if empty will not be used. RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used. - Measurement string `json:"measurement"` // Measurement is the optinally selected measurement for the query + Measurement string `json:"measurement"` // Measurement is the optionally selected measurement for the query TagKey string `json:"tagKey"` // TagKey is the optionally selected tag key for the query FieldKey string `json:"fieldKey"` // FieldKey is the optionally selected field key for the query } From 576615daff6d33bfa916b09e0eb556850e84339b Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Thu, 28 Jun 2018 15:09:32 -0700 Subject: [PATCH 52/59] Remove unused function --- ui/src/tempVars/constants/index.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ui/src/tempVars/constants/index.ts b/ui/src/tempVars/constants/index.ts index 4f3e469105..f7131e04af 100644 --- a/ui/src/tempVars/constants/index.ts +++ b/ui/src/tempVars/constants/index.ts @@ -178,13 +178,6 @@ export const insertTempVar = (query, tempVar) => { export const unMask = query => { return query.replace(/😸/g, ':') } -export const removeUnselectedTemplateValues = templates => { - return templates.map(template => { - const selectedValues = template.values.filter(value => value.selected) - return {...template, values: selectedValues} - }) -} - export const TEMPLATE_RANGE: TimeRange = { upper: null, lower: TEMP_VAR_DASHBOARD_TIME, From 87feb2ca80ec39415d72038b77edc3a0401b70b4 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 28 Jun 2018 15:22:03 -0700 Subject: [PATCH 53/59] Remove unnecessary div --- ui/src/logs/components/LogsGraphContainer.tsx | 4 +--- ui/src/style/pages/logs-viewer.scss | 11 ++--------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/ui/src/logs/components/LogsGraphContainer.tsx b/ui/src/logs/components/LogsGraphContainer.tsx index 94ddd8d5eb..2d0f8586aa 100644 --- a/ui/src/logs/components/LogsGraphContainer.tsx +++ b/ui/src/logs/components/LogsGraphContainer.tsx @@ -3,9 +3,7 @@ import React, {PureComponent} from 'react' class LogsGraphContainer extends PureComponent { public render() { return ( -
-
{this.props.children}
-
+
{this.props.children}
) } } diff --git a/ui/src/style/pages/logs-viewer.scss b/ui/src/style/pages/logs-viewer.scss index 2b59b4722e..19c8902dbf 100644 --- a/ui/src/style/pages/logs-viewer.scss +++ b/ui/src/style/pages/logs-viewer.scss @@ -34,7 +34,7 @@ $severity-debug-intense: $g10-wolf; } .logs-viewer--graph-container { - padding: 22px ($logs-viewer-gutter - 16px) 10px ($logs-viewer-gutter - 16px); + padding: 22px $logs-viewer-gutter 10px $logs-viewer-gutter; height: $logs-viewer-graph-height; @include gradient-v($g2-kevlar, $g0-obsidian); display: flex; @@ -231,14 +231,7 @@ $severity-debug-intense: $g10-wolf; width: 170px; } -// Graph -.logs-viewer--graph { - position: relative; - width: 100%; - height: 100%; - padding: 8px 16px; -} - +// Table Dots .logs-viewer--dot { width: 12px; height: 12px; From 504b4fd6e559a471c983dd930283ca4b86ed25af Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 28 Jun 2018 15:22:49 -0700 Subject: [PATCH 54/59] Make histogram axis labels match dygraphs axis labels --- ui/src/shared/components/HistogramChartAxes.tsx | 2 +- ui/src/shared/components/HistogramChartBars.tsx | 2 +- ui/src/style/components/dygraphs.scss | 8 ++++---- ui/src/style/components/histogram-chart.scss | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ui/src/shared/components/HistogramChartAxes.tsx b/ui/src/shared/components/HistogramChartAxes.tsx index 1923750bd5..346352c02d 100644 --- a/ui/src/shared/components/HistogramChartAxes.tsx +++ b/ui/src/shared/components/HistogramChartAxes.tsx @@ -4,7 +4,7 @@ import {ScaleLinear, ScaleTime} from 'd3-scale' import {Margins} from 'src/types/histogram' const Y_TICK_COUNT = 5 -const Y_TICK_PADDING_RIGHT = 5 +const Y_TICK_PADDING_RIGHT = 7 const X_TICK_COUNT = 10 const X_TICK_PADDING_TOP = 8 diff --git a/ui/src/shared/components/HistogramChartBars.tsx b/ui/src/shared/components/HistogramChartBars.tsx index bbf388d47f..451042798f 100644 --- a/ui/src/shared/components/HistogramChartBars.tsx +++ b/ui/src/shared/components/HistogramChartBars.tsx @@ -4,7 +4,7 @@ import {ScaleLinear, ScaleTime} from 'd3-scale' import {HistogramData, HistogramDatum} from 'src/types/histogram' -const BAR_BORDER_RADIUS = 4 +const BAR_BORDER_RADIUS = 3 const BAR_PADDING_SIDES = 4 interface Props { diff --git a/ui/src/style/components/dygraphs.scss b/ui/src/style/components/dygraphs.scss index fa194b789b..a07992e509 100644 --- a/ui/src/style/components/dygraphs.scss +++ b/ui/src/style/components/dygraphs.scss @@ -48,13 +48,13 @@ } .dygraph-axis-label-y { padding: 0 9px 0 0 !important; - text-align: left !important; + text-align: right !important; left: 0 !important; user-select: none; } .dygraph-axis-label-y2 { padding: 0 0 0 9px !important; - text-align: right !important; + text-align: left !important; user-select: none; } @@ -84,10 +84,10 @@ .graph--hasYLabel { .dygraph-axis-label-y { - padding: 0 1px 0 16px !important; + padding: 0 4px 0 0 !important; } .dygraph-axis-label-y2 { - padding: 0 16px 0 1px !important; + padding: 0 0 0 4px !important; } } diff --git a/ui/src/style/components/histogram-chart.scss b/ui/src/style/components/histogram-chart.scss index c04e41a993..853c81affc 100644 --- a/ui/src/style/components/histogram-chart.scss +++ b/ui/src/style/components/histogram-chart.scss @@ -37,15 +37,15 @@ shape-rendering: crispEdges; fill: $c-amethyst; opacity: 1; - pointer: cursor; + cursor: pointer; shape-rendering: crispEdges; } .histogram-chart--axes, .histogram-chart-skeleton { .x-label, .y-label { - fill: $g13-mist; - font-size: 12px; - font-weight: bold; + fill: $g11-sidewalk; + font-size: 11px; + font-weight: 600; } .x-label { @@ -83,7 +83,7 @@ .histogram-chart-tooltip { padding: 8px; background-color: $g0-obsidian; - border-radius: 3px; + border-radius: $radius-small; @extend %drop-shadow; font-size: 12px; font-weight: 600; From bc30dcc7bc6105dee9a8c1011240c8fc409f1e3d Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 28 Jun 2018 15:25:47 -0700 Subject: [PATCH 55/59] Make navbar label consistent with page heading --- ui/src/side_nav/containers/SideNav.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/side_nav/containers/SideNav.tsx b/ui/src/side_nav/containers/SideNav.tsx index ee609fb5aa..ffb040d32c 100644 --- a/ui/src/side_nav/containers/SideNav.tsx +++ b/ui/src/side_nav/containers/SideNav.tsx @@ -121,7 +121,7 @@ class SideNav extends PureComponent { link="/logs" location={location} > - + Date: Thu, 28 Jun 2018 15:32:02 -0700 Subject: [PATCH 56/59] Unpluralize page title --- ui/src/logs/components/LogViewerHeader.tsx | 2 +- ui/src/side_nav/containers/SideNav.tsx | 2 +- ui/src/style/pages/logs-viewer.scss | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/logs/components/LogViewerHeader.tsx b/ui/src/logs/components/LogViewerHeader.tsx index 46add50d06..2d8dff2f01 100644 --- a/ui/src/logs/components/LogViewerHeader.tsx +++ b/ui/src/logs/components/LogViewerHeader.tsx @@ -42,7 +42,7 @@ class LogViewerHeader extends PureComponent { return ( <> {this.status} - + ) } diff --git a/ui/src/side_nav/containers/SideNav.tsx b/ui/src/side_nav/containers/SideNav.tsx index ffb040d32c..ee609fb5aa 100644 --- a/ui/src/side_nav/containers/SideNav.tsx +++ b/ui/src/side_nav/containers/SideNav.tsx @@ -121,7 +121,7 @@ class SideNav extends PureComponent { link="/logs" location={location} > - + Date: Thu, 28 Jun 2018 12:03:10 -0700 Subject: [PATCH 57/59] Check if page is still mounted before setting interval Co-authored-by: Brandon Farmer --- ui/src/dashboards/containers/DashboardPage.tsx | 2 ++ ui/src/hosts/containers/HostsPage.js | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/src/dashboards/containers/DashboardPage.tsx b/ui/src/dashboards/containers/DashboardPage.tsx index 107e7fb505..6b8f6a45c2 100644 --- a/ui/src/dashboards/containers/DashboardPage.tsx +++ b/ui/src/dashboards/containers/DashboardPage.tsx @@ -173,6 +173,7 @@ class DashboardPage extends Component { const {source, getAnnotationsAsync, timeRange} = this.props if (this.props.autoRefresh !== nextProps.autoRefresh) { clearInterval(this.intervalID) + this.intervalID = null const annotationRange = millisecondTimeRange(timeRange) if (nextProps.autoRefresh) { this.intervalID = window.setInterval(() => { @@ -193,6 +194,7 @@ class DashboardPage extends Component { public componentWillUnmount() { clearInterval(this.intervalID) + this.intervalID = null window.removeEventListener('resize', this.handleWindowResize, true) this.props.handleDismissEditingAnnotation() } diff --git a/ui/src/hosts/containers/HostsPage.js b/ui/src/hosts/containers/HostsPage.js index 887cb50d8b..5cbdffc000 100644 --- a/ui/src/hosts/containers/HostsPage.js +++ b/ui/src/hosts/containers/HostsPage.js @@ -73,6 +73,7 @@ export class HostsPage extends Component { async componentDidMount() { const {notify, autoRefresh} = this.props + this.componentIsMounted = true this.setState({hostsLoading: true}) // Only print this once const results = await getLayouts() @@ -88,7 +89,7 @@ export class HostsPage extends Component { return } await this.fetchHostsData() - if (autoRefresh) { + if (autoRefresh && this.componentIsMounted) { this.intervalID = setInterval(() => this.fetchHostsData(), autoRefresh) } } @@ -151,6 +152,7 @@ export class HostsPage extends Component { } componentWillUnmount() { + this.componentIsMounted = false clearInterval(this.intervalID) this.intervalID = false } From a8200ba4e9ca75c35c1b428af9d1624299be9f58 Mon Sep 17 00:00:00 2001 From: Iris Scholten Date: Thu, 28 Jun 2018 13:10:44 -0700 Subject: [PATCH 58/59] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7773c30b3c..edf05d3f37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ 1. [#3733](https://github.com/influxdata/chronograf/pull/3733): Change arrows in table columns so that ascending sort points up and descending points down 1. [#3751](https://github.com/influxdata/chronograf/pull/3751): Fix crosshairs moving passed the edges of graphs 1. [#3759](https://github.com/influxdata/chronograf/pull/3759): Change y-axis options to have valid defaults +1. [#3793](https://github.com/influxdata/chronograf/pull/3793): Stop making requests for old sources after changing sources ## v1.5.0.0 [2018-05-15-RC] From dd2e84e3b5fbb08b4950da151105a6e36b2670ad Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Thu, 28 Jun 2018 16:55:36 -0700 Subject: [PATCH 59/59] Update test snapshot --- .../HistogramChart.test.tsx.snap | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ui/test/shared/components/__snapshots__/HistogramChart.test.tsx.snap b/ui/test/shared/components/__snapshots__/HistogramChart.test.tsx.snap index 21c90d3427..693f0f9cef 100644 --- a/ui/test/shared/components/__snapshots__/HistogramChart.test.tsx.snap +++ b/ui/test/shared/components/__snapshots__/HistogramChart.test.tsx.snap @@ -250,7 +250,7 @@ exports[`HistogramChart displays the visualization with bars if nonempty data is 0 @@ -258,7 +258,7 @@ exports[`HistogramChart displays the visualization with bars if nonempty data is 0.5 @@ -266,7 +266,7 @@ exports[`HistogramChart displays the visualization with bars if nonempty data is 1 @@ -274,7 +274,7 @@ exports[`HistogramChart displays the visualization with bars if nonempty data is 1.5 @@ -282,7 +282,7 @@ exports[`HistogramChart displays the visualization with bars if nonempty data is 2 @@ -371,9 +371,9 @@ exports[`HistogramChart displays the visualization with bars if nonempty data is id="histogram-chart-bars--clip-1-1-193.5" >