diff --git a/Makefile b/Makefile index 2b511c97ea..3f2b1b0fb4 100644 --- a/Makefile +++ b/Makefile @@ -47,8 +47,18 @@ assets: $(REACT_APP_OUTPUT_DIR) cd web/ui && GO111MODULE=$(GO111MODULE) GOOS= GOARCH= $(GO) generate -x -v $(GOOPTS) @$(GOFMT) -w ./web/ui +.PHONY: react-app-lint +react-app-lint: + @echo ">> running React app linting" + cd $(REACT_APP_PATH) && yarn lint:ci + +.PHONY: react-app-lint-fix +react-app-lint-fix: + @echo ">> running React app linting and fixing errors where possibe" + cd $(REACT_APP_PATH) && yarn lint + .PHONY: react-app-test -react-app-test: $(REACT_APP_NODE_MODULES_PATH) +react-app-test: | $(REACT_APP_NODE_MODULES_PATH) react-app-lint @echo ">> running React app tests" cd $(REACT_APP_PATH) && yarn test --no-watch diff --git a/web/ui/react-app/.eslintrc.json b/web/ui/react-app/.eslintrc.json new file mode 100644 index 0000000000..395ca48af2 --- /dev/null +++ b/web/ui/react-app/.eslintrc.json @@ -0,0 +1,31 @@ +{ + "parser": "@typescript-eslint/parser", + "extends": [ + "react-app", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "rules": { + "@typescript-eslint/camelcase": "warn", + "eol-last": [ + "error", + "always" + ], + "object-curly-spacing": [ + "error", + "always" + ], + "prefer-const": "warn", + "comma-dangle": [ + "error", + { + "arrays": "always-multiline", + "objects": "always-multiline", + "imports": "always-multiline" + } + ] + }, + "plugins": [ + "prettier" + ] +} diff --git a/web/ui/react-app/package.json b/web/ui/react-app/package.json index d204fb3a4e..0f2296b47c 100644 --- a/web/ui/react-app/package.json +++ b/web/ui/react-app/package.json @@ -42,10 +42,14 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "lint:ci": "eslint --quiet \"src/**/*.{ts,tsx}\"", + "lint": "eslint --fix \"src/**/*.{ts,tsx}\"" }, - "eslintConfig": { - "extends": "react-app" + "prettier": { + "singleQuote": true, + "trailingComma": "es5", + "printWidth": 125 }, "browserslist": [ ">0.2%", @@ -56,7 +60,20 @@ "devDependencies": { "@types/flot": "0.0.31", "@types/moment-timezone": "^0.5.10", - "@types/reactstrap": "^8.0.5" + "@types/reactstrap": "^8.0.5", + "@typescript-eslint/eslint-plugin": "2.x", + "@typescript-eslint/parser": "2.x", + "babel-eslint": "10.x", + "eslint": "6.x", + "eslint-config-prettier": "^6.4.0", + "eslint-config-react-app": "^5.0.2", + "eslint-plugin-flowtype": "3.x", + "eslint-plugin-import": "2.x", + "eslint-plugin-jsx-a11y": "6.x", + "eslint-plugin-prettier": "^3.1.1", + "eslint-plugin-react": "7.x", + "eslint-plugin-react-hooks": "1.x", + "prettier": "^1.18.2" }, "proxy": "http://localhost:9090" } diff --git a/web/ui/react-app/src/App.tsx b/web/ui/react-app/src/App.tsx index 2df725db01..56c1afa4c4 100755 --- a/web/ui/react-app/src/App.tsx +++ b/web/ui/react-app/src/App.tsx @@ -1,19 +1,10 @@ import React, { Component } from 'react'; -import Navigation from "./Navbar"; +import Navigation from './Navbar'; import { Container } from 'reactstrap'; import './App.css'; import { Router } from '@reach/router'; -import { - Alerts, - Config, - Flags, - Rules, - Services, - Status, - Targets, - PanelList, -} from './pages'; +import { Alerts, Config, Flags, Rules, Services, Status, Targets, PanelList } from './pages'; class App extends Component { render() { diff --git a/web/ui/react-app/src/Checkbox.tsx b/web/ui/react-app/src/Checkbox.tsx index 247416baa7..e988628f62 100644 --- a/web/ui/react-app/src/Checkbox.tsx +++ b/web/ui/react-app/src/Checkbox.tsx @@ -13,7 +13,7 @@ const Checkbox: FC = ({ children, wrapperStyles, id, ...rest }) = {children} - ) -} + ); +}; export default memo(Checkbox); diff --git a/web/ui/react-app/src/DataTable.tsx b/web/ui/react-app/src/DataTable.tsx index dd184a51f4..b6012bffc9 100644 --- a/web/ui/react-app/src/DataTable.tsx +++ b/web/ui/react-app/src/DataTable.tsx @@ -5,33 +5,38 @@ import { Alert, Table } from 'reactstrap'; import SeriesName from './SeriesName'; export interface QueryResult { - data: null | { - resultType: 'vector', - result: InstantSample[], - } | { - resultType: 'matrix', - result: RangeSamples[], - } | { - resultType: 'scalar', - result: SampleValue, - } | { - resultType: 'string', - result: string, - }, -}; + data: + | null + | { + resultType: 'vector'; + result: InstantSample[]; + } + | { + resultType: 'matrix'; + result: RangeSamples[]; + } + | { + resultType: 'scalar'; + result: SampleValue; + } + | { + resultType: 'string'; + result: string; + }; +} interface InstantSample { - metric: Metric, - value: SampleValue, + metric: Metric; + value: SampleValue; } interface RangeSamples { - metric: Metric, - values: SampleValue[], + metric: Metric; + values: SampleValue[]; } interface Metric { - [key: string]: string, + [key: string]: string; } type SampleValue = [number, string]; @@ -59,29 +64,55 @@ class DataTable extends PureComponent { let rows: ReactNode[] = []; let limited = false; - switch(data.resultType) { + switch (data.resultType) { case 'vector': - rows = (this.limitSeries(data.result) as InstantSample[]) - .map((s: InstantSample, index: number): ReactNode => { - return {s.value[1]}; - }); + rows = (this.limitSeries(data.result) as InstantSample[]).map( + (s: InstantSample, index: number): ReactNode => { + return ( + + + + + {s.value[1]} + + ); + } + ); limited = rows.length !== data.result.length; break; case 'matrix': - rows = (this.limitSeries(data.result) as RangeSamples[]) - .map((s, index) => { - const valueText = s.values.map((v) => { + rows = (this.limitSeries(data.result) as RangeSamples[]).map((s, index) => { + const valueText = s.values + .map(v => { return [1] + ' @' + v[0]; - }).join('\n'); - return {valueText}; - }); + }) + .join('\n'); + return ( + + + + + {valueText} + + ); + }); limited = rows.length !== data.result.length; break; case 'scalar': - rows.push(scalar{data.result[1]}); + rows.push( + + scalar + {data.result[1]} + + ); break; case 'string': - rows.push(scalar{data.result[1]}); + rows.push( + + scalar + {data.result[1]} + + ); break; default: return Unsupported result value type; @@ -89,15 +120,13 @@ class DataTable extends PureComponent { return ( <> - {limited && + {limited && ( Warning: Fetched {data.result.length} metrics, only displaying first {rows.length}. - } + )} - - {rows} - + {rows}
); diff --git a/web/ui/react-app/src/ExpressionInput.tsx b/web/ui/react-app/src/ExpressionInput.tsx index 1a9e33645f..9487d14c81 100644 --- a/web/ui/react-app/src/ExpressionInput.tsx +++ b/web/ui/react-app/src/ExpressionInput.tsx @@ -37,8 +37,8 @@ class ExpressionInput extends Component { - this.setState({ - height: 'auto', - value: this.exprInputRef.current!.value - }, this.setHeight); - } + this.setState( + { + height: 'auto', + value: this.exprInputRef.current!.value, + }, + this.setHeight + ); + }; handleDropdownSelection = (value: string) => { this.setState({ value, height: 'auto' }, this.setHeight); @@ -67,50 +70,54 @@ class ExpressionInput extends Component this.props.executeQuery(this.exprInputRef.current!.value); getSearchMatches = (input: string, expressions: string[]) => { return fuzzy.filter(input.replace(/ /g, ''), expressions, { - pre: "", - post: "", + pre: '', + post: '', }); - } + }; createAutocompleteSection = (downshift: ControllerStateAndHelpers) => { const { inputValue = '', closeMenu, highlightedIndex } = downshift; const { autocompleteSections } = this.props; let index = 0; - const sections = inputValue!.length ? - Object.entries(autocompleteSections).reduce((acc, [title, items]) => { - const matches = this.getSearchMatches(inputValue!, items); - return !matches.length ? acc : [ - ...acc, - - {title} - { - matches - .slice(0, 100) // Limit DOM rendering to 100 results, as DOM rendering is sloooow. - .map(({ original, string }) => { - const itemProps = downshift.getItemProps({ - key: original, - index, - item: original, - style: { - backgroundColor: highlightedIndex === index++ ? 'lightgray' : 'white', - }, - }) - return ( - - {string} - - ); - }) - } - - ] - }, [] as JSX.Element[]) : [] + const sections = inputValue!.length + ? Object.entries(autocompleteSections).reduce( + (acc, [title, items]) => { + const matches = this.getSearchMatches(inputValue!, items); + return !matches.length + ? acc + : [ + ...acc, + + {title} + {matches + .slice(0, 100) // Limit DOM rendering to 100 results, as DOM rendering is sloooow. + .map(({ original, string }) => { + const itemProps = downshift.getItemProps({ + key: original, + index, + item: original, + style: { + backgroundColor: highlightedIndex === index++ ? 'lightgray' : 'white', + }, + }); + return ( + + {string} + + ); + })} + , + ]; + }, + [] as JSX.Element[] + ) + : []; if (!sections.length) { // This is ugly but is needed in order to sync state updates. @@ -121,19 +128,16 @@ class ExpressionInput extends Component - { sections } + {sections} ); - } + }; render() { const { value, height } = this.state; return ( - - {(downshift) => ( + + {downshift => (
@@ -175,15 +179,11 @@ class ExpressionInput extends Component - diff --git a/web/ui/react-app/src/Graph.tsx b/web/ui/react-app/src/Graph.tsx index 3b060e75b5..f7f6b49a7b 100644 --- a/web/ui/react-app/src/Graph.tsx +++ b/web/ui/react-app/src/Graph.tsx @@ -12,7 +12,7 @@ require('flot/source/jquery.flot.time'); require('flot/source/jquery.canvaswrapper'); require('jquery.flot.tooltip'); -var graphID = 0; +let graphID = 0; function getGraphID() { // TODO: This is ugly. return graphID++; @@ -22,9 +22,9 @@ interface GraphProps { data: any; // TODO: Type this. stacked: boolean; queryParams: { - startTime: number, - endTime: number, - resolution: number, + startTime: number; + endTime: number; + resolution: number; } | null; } @@ -33,76 +33,76 @@ class Graph extends PureComponent { private chartRef = React.createRef(); escapeHTML(str: string) { - var entityMap: {[key: string]: string} = { + const entityMap: { [key: string]: string } = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', - '/': '/' + '/': '/', }; - return String(str).replace(/[&<>"'/]/g, function (s) { + return String(str).replace(/[&<>"'/]/g, function(s) { return entityMap[s]; }); } - renderLabels(labels: {[key: string]: string}) { - let labelStrings: string[] = []; - for (let label in labels) { + renderLabels(labels: { [key: string]: string }) { + const labelStrings: string[] = []; + for (const label in labels) { if (label !== '__name__') { labelStrings.push('' + label + ': ' + this.escapeHTML(labels[label])); } } return '
' + labelStrings.join('
') + '
'; - }; + } formatValue = (y: number | null): string => { if (y === null) { return 'null'; } - var abs_y = Math.abs(y); + const abs_y = Math.abs(y); if (abs_y >= 1e24) { - return (y / 1e24).toFixed(2) + "Y"; + return (y / 1e24).toFixed(2) + 'Y'; } else if (abs_y >= 1e21) { - return (y / 1e21).toFixed(2) + "Z"; + return (y / 1e21).toFixed(2) + 'Z'; } else if (abs_y >= 1e18) { - return (y / 1e18).toFixed(2) + "E"; + return (y / 1e18).toFixed(2) + 'E'; } else if (abs_y >= 1e15) { - return (y / 1e15).toFixed(2) + "P"; + return (y / 1e15).toFixed(2) + 'P'; } else if (abs_y >= 1e12) { - return (y / 1e12).toFixed(2) + "T"; + return (y / 1e12).toFixed(2) + 'T'; } else if (abs_y >= 1e9) { - return (y / 1e9).toFixed(2) + "G"; + return (y / 1e9).toFixed(2) + 'G'; } else if (abs_y >= 1e6) { - return (y / 1e6).toFixed(2) + "M"; + return (y / 1e6).toFixed(2) + 'M'; } else if (abs_y >= 1e3) { - return (y / 1e3).toFixed(2) + "k"; + return (y / 1e3).toFixed(2) + 'k'; } else if (abs_y >= 1) { - return y.toFixed(2) + return y.toFixed(2); } else if (abs_y === 0) { - return y.toFixed(2) + return y.toFixed(2); } else if (abs_y <= 1e-24) { - return (y / 1e-24).toFixed(2) + "y"; + return (y / 1e-24).toFixed(2) + 'y'; } else if (abs_y <= 1e-21) { - return (y / 1e-21).toFixed(2) + "z"; + return (y / 1e-21).toFixed(2) + 'z'; } else if (abs_y <= 1e-18) { - return (y / 1e-18).toFixed(2) + "a"; + return (y / 1e-18).toFixed(2) + 'a'; } else if (abs_y <= 1e-15) { - return (y / 1e-15).toFixed(2) + "f"; + return (y / 1e-15).toFixed(2) + 'f'; } else if (abs_y <= 1e-12) { - return (y / 1e-12).toFixed(2) + "p"; + return (y / 1e-12).toFixed(2) + 'p'; } else if (abs_y <= 1e-9) { - return (y / 1e-9).toFixed(2) + "n"; + return (y / 1e-9).toFixed(2) + 'n'; } else if (abs_y <= 1e-6) { - return (y / 1e-6).toFixed(2) + "µ"; - } else if (abs_y <=1e-3) { - return (y / 1e-3).toFixed(2) + "m"; + return (y / 1e-6).toFixed(2) + 'µ'; + } else if (abs_y <= 1e-3) { + return (y / 1e-3).toFixed(2) + 'm'; } else if (abs_y <= 1) { - return y.toFixed(2) + return y.toFixed(2); } throw Error("couldn't format a value, this is a bug"); - } + }; getOptions(): any { return { @@ -133,9 +133,9 @@ class Graph extends PureComponent { cssClass: 'graph-tooltip', content: (label: string, xval: number, yval: number, flotItem: any) => { const series = flotItem.series; // TODO: type this. - var date = '' + new Date(xval).toUTCString() + ''; - var swatch = ''; - var content = swatch + (series.labels.__name__ || 'value') + ": " + yval + ''; + const date = '' + new Date(xval).toUTCString() + ''; + const swatch = ''; + const content = swatch + (series.labels.__name__ || 'value') + ': ' + yval + ''; return date + '
' + content + '
' + this.renderLabels(series.labels); }, defaultTheme: false, @@ -149,20 +149,20 @@ class Graph extends PureComponent { fill: this.props.stacked, }, shadowSize: 0, - } + }, }; } // This was adapted from Flot's color generation code. getColors() { - let colors = []; - const colorPool = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"]; + const colors = []; + const colorPool = ['#edc240', '#afd8f8', '#cb4b4b', '#4da74d', '#9440ed']; const colorPoolSize = colorPool.length; let variation = 0; const neededColors = this.props.data.result.length; for (let i = 0; i < neededColors; i++) { - const c = ($ as any).color.parse(colorPool[i % colorPoolSize] || "#666"); + const c = ($ as any).color.parse(colorPool[i % colorPoolSize] || '#666'); // Each time we exhaust the colors in the pool we adjust // a scaling factor used to produce more variations on @@ -191,7 +191,7 @@ class Graph extends PureComponent { return this.props.data.result.map((ts: any /* TODO: Type this*/, index: number) => { // Insert nulls for all missing steps. - let data = []; + const data = []; let pos = 0; const params = this.props.queryParams!; @@ -214,11 +214,11 @@ class Graph extends PureComponent { color: colors[index], index: index, }; - }) + }); } parseValue(value: string) { - var val = parseFloat(value); + const val = parseFloat(value); if (isNaN(val)) { // "+Inf", "-Inf", "+Inf" will be parsed into NaN by parseFloat(). They // can't be graphed, so show them as gaps (null). @@ -229,7 +229,7 @@ class Graph extends PureComponent { return this.props.stacked ? 0 : null; } return val; - }; + } componentDidMount() { this.plot(); @@ -264,7 +264,11 @@ class Graph extends PureComponent { } if (this.props.data.resultType !== 'matrix') { - return Query result is of wrong type '{this.props.data.resultType}', should be 'matrix' (range vector).; + return ( + + Query result is of wrong type '{this.props.data.resultType}', should be 'matrix' (range vector). + + ); } if (this.props.data.result.length === 0) { @@ -275,7 +279,7 @@ class Graph extends PureComponent {
this.plot()} />
- +
); } diff --git a/web/ui/react-app/src/GraphControls.tsx b/web/ui/react-app/src/GraphControls.tsx index 339b54dc83..3f494ae4fc 100644 --- a/web/ui/react-app/src/GraphControls.tsx +++ b/web/ui/react-app/src/GraphControls.tsx @@ -1,20 +1,8 @@ import React, { Component } from 'react'; -import { - Button, - ButtonGroup, - Form, - InputGroup, - InputGroupAddon, - Input, -} from 'reactstrap'; +import { Button, ButtonGroup, Form, InputGroup, InputGroupAddon, Input } from 'reactstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { - faPlus, - faMinus, - faChartArea, - faChartLine, -} from '@fortawesome/free-solid-svg-icons'; +import { faPlus, faMinus, faChartArea, faChartLine } from '@fortawesome/free-solid-svg-icons'; import TimeInput from './TimeInput'; import { parseRange, formatRange } from './utils/timeFormat'; @@ -39,22 +27,22 @@ class GraphControls extends Component { 1, 10, 60, - 5*60, - 15*60, - 30*60, - 60*60, - 2*60*60, - 6*60*60, - 12*60*60, - 24*60*60, - 48*60*60, - 7*24*60*60, - 14*24*60*60, - 28*24*60*60, - 56*24*60*60, - 365*24*60*60, - 730*24*60*60, - ] + 5 * 60, + 15 * 60, + 30 * 60, + 60 * 60, + 2 * 60 * 60, + 6 * 60 * 60, + 12 * 60 * 60, + 24 * 60 * 60, + 48 * 60 * 60, + 7 * 24 * 60 * 60, + 14 * 24 * 60 * 60, + 28 * 24 * 60 * 60, + 56 * 24 * 60 * 60, + 365 * 24 * 60 * 60, + 730 * 24 * 60 * 60, + ]; onChangeRangeInput = (rangeText: string): void => { const range = parseRange(rangeText); @@ -63,31 +51,31 @@ class GraphControls extends Component { } else { this.props.onChangeRange(range); } - } + }; changeRangeInput = (range: number): void => { this.rangeRef.current!.value = formatRange(range); - } + }; increaseRange = (): void => { - for (let range of this.rangeSteps) { + for (const range of this.rangeSteps) { if (this.props.range < range) { this.changeRangeInput(range); this.props.onChangeRange(range); return; } } - } + }; decreaseRange = (): void => { - for (let range of this.rangeSteps.slice().reverse()) { + for (const range of this.rangeSteps.slice().reverse()) { if (this.props.range > range) { this.changeRangeInput(range); this.props.onChangeRange(range); return; } } - } + }; componentDidUpdate(prevProps: GraphControlsProps) { if (prevProps.range !== this.props.range) { @@ -103,7 +91,9 @@ class GraphControls extends Component {
e.preventDefault()}> - + { /> - + @@ -137,8 +129,16 @@ class GraphControls extends Component { /> - - + + ); diff --git a/web/ui/react-app/src/Legend.tsx b/web/ui/react-app/src/Legend.tsx index a113b569aa..88cd2c4a0d 100644 --- a/web/ui/react-app/src/Legend.tsx +++ b/web/ui/react-app/src/Legend.tsx @@ -11,7 +11,7 @@ class Legend extends PureComponent { return ( -
+
@@ -24,7 +24,9 @@ class Legend extends PureComponent { return ( - {this.props.series.map((s: any) => {return this.renderLegendItem(s)})} + {this.props.series.map((s: any) => { + return this.renderLegendItem(s); + })}
); diff --git a/web/ui/react-app/src/MetricFomat.ts b/web/ui/react-app/src/MetricFomat.ts index 7558b3005c..2b636736c0 100644 --- a/web/ui/react-app/src/MetricFomat.ts +++ b/web/ui/react-app/src/MetricFomat.ts @@ -1,16 +1,16 @@ -function metricToSeriesName(labels: {[key: string]: string}): string { +function metricToSeriesName(labels: { [key: string]: string }): string { if (labels === null) { return 'scalar'; } let tsName = (labels.__name__ || '') + '{'; - let labelStrings: string[] = []; - for (let label in labels) { + const labelStrings: string[] = []; + for (const label in labels) { if (label !== '__name__') { labelStrings.push(label + '="' + labels[label] + '"'); } } tsName += labelStrings.join(', ') + '}'; return tsName; -}; +} export default metricToSeriesName; diff --git a/web/ui/react-app/src/Navbar.tsx b/web/ui/react-app/src/Navbar.tsx index d6081d128e..92cdaf5eab 100644 --- a/web/ui/react-app/src/Navbar.tsx +++ b/web/ui/react-app/src/Navbar.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Link } from "@reach/router"; +import { Link } from '@reach/router'; import { Collapse, Navbar, @@ -18,37 +18,59 @@ const Navigation = () => { const toggle = () => setIsOpen(!isOpen); return ( - - Prometheus + + + Prometheus + ); -} +}; export default Navigation; diff --git a/web/ui/react-app/src/Panel.tsx b/web/ui/react-app/src/Panel.tsx index d0b0c77b4e..8c717139ad 100644 --- a/web/ui/react-app/src/Panel.tsx +++ b/web/ui/react-app/src/Panel.tsx @@ -1,16 +1,6 @@ import React, { Component } from 'react'; -import { - Alert, - Button, - Col, - Nav, - NavItem, - NavLink, - Row, - TabContent, - TabPane, -} from 'reactstrap'; +import { Alert, Button, Col, Nav, NavItem, NavLink, Row, TabContent, TabPane } from 'reactstrap'; import moment from 'moment-timezone'; @@ -32,14 +22,15 @@ interface PanelProps { interface PanelState { data: any; // TODO: Type data. - lastQueryParams: { // TODO: Share these with Graph.tsx in a file. - startTime: number, - endTime: number, - resolution: number, + lastQueryParams: { + // TODO: Share these with Graph.tsx in a file. + startTime: number; + endTime: number; + resolution: number; } | null; loading: boolean; error: string | null; - stats: QueryStats | null, + stats: QueryStats | null; } export interface PanelOptions { @@ -63,7 +54,7 @@ export const PanelDefaultOptions: PanelOptions = { endTime: null, resolution: null, stacked: false, -} +}; class Panel extends Component { private abortInFlightFetch: (() => void) | null = null; @@ -83,16 +74,17 @@ class Panel extends Component { componentDidUpdate(prevProps: PanelProps, prevState: PanelState) { const prevOpts = prevProps.options; const opts = this.props.options; - if (prevOpts.type !== opts.type || - prevOpts.range !== opts.range || - prevOpts.endTime !== opts.endTime || - prevOpts.resolution !== opts.resolution) { - + if ( + prevOpts.type !== opts.type || + prevOpts.range !== opts.range || + prevOpts.endTime !== opts.endTime || + prevOpts.resolution !== opts.resolution + ) { if (prevOpts.type !== opts.type) { // If the other options change, we still want to show the old data until the new // query completes, but this is not a good idea when we actually change between // table and graph view, since not all queries work well in both. - this.setState({data: null}); + this.setState({ data: null }); } this.executeQuery(opts.expr); } @@ -104,9 +96,9 @@ class Panel extends Component { executeQuery = (expr: string): void => { const queryStart = Date.now(); - this.props.onExecuteQuery(expr) + this.props.onExecuteQuery(expr); if (this.props.options.expr !== expr) { - this.setOptions({expr: expr}); + this.setOptions({ expr: expr }); } if (expr === '') { return; @@ -119,114 +111,114 @@ class Panel extends Component { const abortController = new AbortController(); this.abortInFlightFetch = () => abortController.abort(); - this.setState({loading: true}); + this.setState({ loading: true }); const endTime = this.getEndTime().valueOf() / 1000; // TODO: shouldn't valueof only work when it's a moment? const startTime = endTime - this.props.options.range; const resolution = this.props.options.resolution || Math.max(Math.floor(this.props.options.range / 250), 1); const url = new URL(window.location.href); - const params: {[key: string]: string} = { - 'query': expr, + const params: { [key: string]: string } = { + query: expr, }; switch (this.props.options.type) { case 'graph': - url.pathname = '../../api/v1/query_range' + url.pathname = '../../api/v1/query_range'; Object.assign(params, { start: startTime, end: endTime, step: resolution, - }) + }); // TODO path prefix here and elsewhere. break; case 'table': - url.pathname = '../../api/v1/query' + url.pathname = '../../api/v1/query'; Object.assign(params, { time: endTime, - }) + }); break; default: throw new Error('Invalid panel type "' + this.props.options.type + '"'); } - Object.keys(params).forEach(key => url.searchParams.append(key, params[key])) + Object.keys(params).forEach(key => url.searchParams.append(key, params[key])); - fetch(url.toString(), {cache: 'no-store', signal: abortController.signal}) - .then(resp => resp.json()) - .then(json => { - if (json.status !== 'success') { - throw new Error(json.error || 'invalid response JSON'); - } - - let resultSeries = 0; - if (json.data) { - const { resultType, result } = json.data; - if (resultType === "scalar") { - resultSeries = 1; - } else if (result && result.length > 0) { - resultSeries = result.length; + fetch(url.toString(), { cache: 'no-store', signal: abortController.signal }) + .then(resp => resp.json()) + .then(json => { + if (json.status !== 'success') { + throw new Error(json.error || 'invalid response JSON'); } - } - this.setState({ - error: null, - data: json.data, - lastQueryParams: { - startTime, - endTime, - resolution, - }, - stats: { - loadTime: Date.now() - queryStart, - resolution, - resultSeries - }, - loading: false, - }); - this.abortInFlightFetch = null; - }) - .catch(error => { - if (error.name === 'AbortError') { - // Aborts are expected, don't show an error for them. - return - } - this.setState({ - error: 'Error executing query: ' + error.message, - loading: false, + let resultSeries = 0; + if (json.data) { + const { resultType, result } = json.data; + if (resultType === 'scalar') { + resultSeries = 1; + } else if (result && result.length > 0) { + resultSeries = result.length; + } + } + + this.setState({ + error: null, + data: json.data, + lastQueryParams: { + startTime, + endTime, + resolution, + }, + stats: { + loadTime: Date.now() - queryStart, + resolution, + resultSeries, + }, + loading: false, + }); + this.abortInFlightFetch = null; }) - }); - } + .catch(error => { + if (error.name === 'AbortError') { + // Aborts are expected, don't show an error for them. + return; + } + this.setState({ + error: 'Error executing query: ' + error.message, + loading: false, + }); + }); + }; setOptions(opts: object): void { - const newOpts = {...this.props.options, ...opts}; + const newOpts = { ...this.props.options, ...opts }; this.props.onOptionsChanged(newOpts); } handleExpressionChange = (expr: string): void => { - this.setOptions({expr: expr}); - } + this.setOptions({ expr: expr }); + }; handleChangeRange = (range: number): void => { - this.setOptions({range: range}); - } + this.setOptions({ range: range }); + }; getEndTime = (): number | moment.Moment => { if (this.props.options.endTime === null) { return moment(); } return this.props.options.endTime; - } + }; handleChangeEndTime = (endTime: number | null) => { - this.setOptions({endTime: endTime}); - } + this.setOptions({ endTime: endTime }); + }; handleChangeResolution = (resolution: number | null) => { - this.setOptions({resolution: resolution}); - } + this.setOptions({ resolution: resolution }); + }; handleChangeStacking = (stacked: boolean) => { - this.setOptions({stacked: stacked}); - } + this.setOptions({ stacked: stacked }); + }; render() { const { pastQueries, metricNames, options } = this.props; @@ -246,9 +238,7 @@ class Panel extends Component { - - {this.state.error && {this.state.error}} - + {this.state.error && {this.state.error}} @@ -256,7 +246,9 @@ class Panel extends Component { { this.setOptions({type: 'table'}); }} + onClick={() => { + this.setOptions({ type: 'table' }); + }} > Table @@ -264,19 +256,18 @@ class Panel extends Component { { this.setOptions({type: 'graph'}); }} + onClick={() => { + this.setOptions({ type: 'graph' }); + }} > Graph - { - (!this.state.loading && !this.state.error && this.state.stats) && - - } + {!this.state.loading && !this.state.error && this.state.stats && } - {options.type === 'table' && + {options.type === 'table' && ( <>
{
- } + )}
- {this.props.options.type === 'graph' && + {this.props.options.type === 'graph' && ( <> { /> - } + )}
- +
diff --git a/web/ui/react-app/src/QueryStatsView.tsx b/web/ui/react-app/src/QueryStatsView.tsx index a7cca4da81..ba6db013cd 100644 --- a/web/ui/react-app/src/QueryStatsView.tsx +++ b/web/ui/react-app/src/QueryStatsView.tsx @@ -2,19 +2,21 @@ import React, { FC } from 'react'; import './QueryStatsView.css'; export interface QueryStats { - loadTime: number; - resolution: number; - resultSeries: number; + loadTime: number; + resolution: number; + resultSeries: number; } const QueryStatsView: FC = props => { - const {loadTime, resolution, resultSeries} = props; + const { loadTime, resolution, resultSeries } = props; - return ( -
- Load time: {loadTime}ms   Resolution: {resolution}s   Result series: {resultSeries} -
- ); -} + return ( +
+ + Load time: {loadTime}ms   Resolution: {resolution}s   Result series: {resultSeries} + +
+ ); +}; export default QueryStatsView; diff --git a/web/ui/react-app/src/SeriesName.tsx b/web/ui/react-app/src/SeriesName.tsx index 459aa84cf8..1da4882570 100644 --- a/web/ui/react-app/src/SeriesName.tsx +++ b/web/ui/react-app/src/SeriesName.tsx @@ -1,7 +1,7 @@ -import React, { PureComponent } from "react"; +import React, { PureComponent } from 'react'; interface SeriesNameProps { - labels: {[key: string]: string} | null; + labels: { [key: string]: string } | null; format: boolean; } @@ -9,9 +9,9 @@ class SeriesName extends PureComponent { renderFormatted(): React.ReactNode { const labels = this.props.labels!; - let labelNodes: React.ReactNode[] = []; + const labelNodes: React.ReactNode[] = []; let first = true; - for (let label in labels) { + for (const label in labels) { if (label === '__name__') { continue; } @@ -19,8 +19,7 @@ class SeriesName extends PureComponent { labelNodes.push( {!first && ', '} - {label}= - "{labels[label]}" + {label}="{labels[label]}" ); @@ -33,7 +32,7 @@ class SeriesName extends PureComponent { <> {labels.__name__ || ''} {'{'} - {labelNodes} + {labelNodes} {'}'} ); @@ -43,8 +42,8 @@ class SeriesName extends PureComponent { const labels = this.props.labels!; let tsName = (labels.__name__ || '') + '{'; - let labelStrings: string[] = []; - for (let label in labels) { + const labelStrings: string[] = []; + for (const label in labels) { if (label !== '__name__') { labelStrings.push(label + '="' + labels[label] + '"'); } diff --git a/web/ui/react-app/src/TimeInput.tsx b/web/ui/react-app/src/TimeInput.tsx index 9034714234..a9549b344d 100644 --- a/web/ui/react-app/src/TimeInput.tsx +++ b/web/ui/react-app/src/TimeInput.tsx @@ -19,12 +19,7 @@ import { faTimes, } from '@fortawesome/free-solid-svg-icons'; -library.add( - faCalendarCheck, - faArrowUp, - faArrowDown, - faTimes, -); +library.add(faCalendarCheck, faArrowUp, faArrowDown, faTimes); // Sadly needed to also replace within the date picker, since it's not a React component. dom.watch(); @@ -42,21 +37,21 @@ class TimeInput extends Component { getBaseTime = (): number => { return this.props.time || moment().valueOf(); - } + }; increaseTime = (): void => { - const time = this.getBaseTime() + this.props.range*1000/2; + const time = this.getBaseTime() + (this.props.range * 1000) / 2; this.props.onChangeTime(time); - } + }; decreaseTime = (): void => { - const time = this.getBaseTime() - this.props.range*1000/2; + const time = this.getBaseTime() - (this.props.range * 1000) / 2; this.props.onChangeTime(time); - } + }; clearTime = (): void => { this.props.onChangeTime(null); - } + }; componentDidMount() { this.$time = $(this.timeInputRef.current!); @@ -96,7 +91,9 @@ class TimeInput extends Component { return ( - + { innerRef={this.timeInputRef} onFocus={() => this.$time.datetimepicker('show')} onBlur={() => this.$time.datetimepicker('hide')} - onKeyDown={(e) => ['Escape', 'Enter'].includes(e.key) && this.$time.datetimepicker('hide')} + onKeyDown={e => ['Escape', 'Enter'].includes(e.key) && this.$time.datetimepicker('hide')} /> {/* CAUTION: While the datetimepicker also has an option to show a 'clear' button, that functionality is broken, so we create an external solution instead. */} - {this.props.time && + {this.props.time && ( - + - } + )} - + ); diff --git a/web/ui/react-app/src/pages/Alerts.tsx b/web/ui/react-app/src/pages/Alerts.tsx index ccb00a6447..72ae2e1b81 100644 --- a/web/ui/react-app/src/pages/Alerts.tsx +++ b/web/ui/react-app/src/pages/Alerts.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { RouteComponentProps } from '@reach/router'; -const Alerts: FC = props =>
Alerts page
+const Alerts: FC = props =>
Alerts page
; export default Alerts; diff --git a/web/ui/react-app/src/pages/Config.tsx b/web/ui/react-app/src/pages/Config.tsx index 6c302d64e0..a531a5fdb7 100644 --- a/web/ui/react-app/src/pages/Config.tsx +++ b/web/ui/react-app/src/pages/Config.tsx @@ -9,7 +9,7 @@ import './Config.css'; const Config: FC = () => { const [config, setConfig] = useState(null); - const [error, setError] = useState(""); + const [error, setError] = useState(''); const [copied, setCopied] = useState(false); useEffect(() => { @@ -25,7 +25,10 @@ const Config: FC = () => { Configuration  { setCopied(result); setTimeout(setCopied, 1500);}} + onCopy={(text, result) => { + setCopied(result); + setTimeout(setCopied, 1500); + }} > + ))} + ); } diff --git a/web/ui/react-app/src/pages/Rules.tsx b/web/ui/react-app/src/pages/Rules.tsx index e6bb89ca9d..13fa5bf727 100644 --- a/web/ui/react-app/src/pages/Rules.tsx +++ b/web/ui/react-app/src/pages/Rules.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { RouteComponentProps } from '@reach/router'; -const Rules: FC = () =>
Rules page
+const Rules: FC = () =>
Rules page
; export default Rules; diff --git a/web/ui/react-app/src/pages/Services.tsx b/web/ui/react-app/src/pages/Services.tsx index 4f09561a13..0f7d71bb9f 100644 --- a/web/ui/react-app/src/pages/Services.tsx +++ b/web/ui/react-app/src/pages/Services.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { RouteComponentProps } from '@reach/router'; -const Services: FC = () =>
Services page
+const Services: FC = () =>
Services page
; export default Services; diff --git a/web/ui/react-app/src/pages/Status.tsx b/web/ui/react-app/src/pages/Status.tsx index ed5c14b1fa..7fbaffac41 100644 --- a/web/ui/react-app/src/pages/Status.tsx +++ b/web/ui/react-app/src/pages/Status.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { RouteComponentProps } from '@reach/router'; -const Status: FC = () =>
Status page
+const Status: FC = () =>
Status page
; export default Status; diff --git a/web/ui/react-app/src/pages/Targets.tsx b/web/ui/react-app/src/pages/Targets.tsx index 4df181313b..11ab810a9e 100644 --- a/web/ui/react-app/src/pages/Targets.tsx +++ b/web/ui/react-app/src/pages/Targets.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { RouteComponentProps } from '@reach/router'; -const Targets: FC = () =>
Targets page
+const Targets: FC = () =>
Targets page
; export default Targets; diff --git a/web/ui/react-app/src/pages/index.ts b/web/ui/react-app/src/pages/index.ts index 4bdf2cbfbb..f286ed750c 100644 --- a/web/ui/react-app/src/pages/index.ts +++ b/web/ui/react-app/src/pages/index.ts @@ -7,13 +7,4 @@ import Status from './Status'; import Targets from './Targets'; import PanelList from './PanelList'; -export { - Alerts, - Config, - Flags, - Rules, - Services, - Status, - Targets, - PanelList, -} +export { Alerts, Config, Flags, Rules, Services, Status, Targets, PanelList }; diff --git a/web/ui/react-app/src/utils/func.ts b/web/ui/react-app/src/utils/func.ts index 9ad243e66c..3b0412fb49 100644 --- a/web/ui/react-app/src/utils/func.ts +++ b/web/ui/react-app/src/utils/func.ts @@ -1 +1,5 @@ -export const uuidGen = () => '_' + Math.random().toString(36).substr(2, 9); +export const uuidGen = () => + '_' + + Math.random() + .toString(36) + .substr(2, 9); diff --git a/web/ui/react-app/src/utils/timeFormat.ts b/web/ui/react-app/src/utils/timeFormat.ts index 22cf9b3fe6..6e39918ac9 100644 --- a/web/ui/react-app/src/utils/timeFormat.ts +++ b/web/ui/react-app/src/utils/timeFormat.ts @@ -1,13 +1,13 @@ import moment from 'moment-timezone'; -const rangeUnits: {[unit: string]: number} = { - 'y': 60 * 60 * 24 * 365, - 'w': 60 * 60 * 24 * 7, - 'd': 60 * 60 * 24, - 'h': 60 * 60, - 'm': 60, - 's': 1 -} +const rangeUnits: { [unit: string]: number } = { + y: 60 * 60 * 24 * 365, + w: 60 * 60 * 24 * 7, + d: 60 * 60 * 24, + h: 60 * 60, + m: 60, + s: 1, +}; export function parseRange(rangeText: string): number | null { const rangeRE = new RegExp('^([0-9]+)([ywdhms]+)$'); @@ -21,9 +21,9 @@ export function parseRange(rangeText: string): number | null { } export function formatRange(range: number): string { - for (let unit of Object.keys(rangeUnits)) { + for (const unit of Object.keys(rangeUnits)) { if (range % rangeUnits[unit] === 0) { - return (range / rangeUnits[unit]) + unit; + return range / rangeUnits[unit] + unit; } } return range + 's'; diff --git a/web/ui/react-app/src/utils/urlParams.ts b/web/ui/react-app/src/utils/urlParams.ts index 6a25598e95..38cc62813e 100644 --- a/web/ui/react-app/src/utils/urlParams.ts +++ b/web/ui/react-app/src/utils/urlParams.ts @@ -1,7 +1,7 @@ import { parseRange, parseTime, formatRange, formatTime } from './timeFormat'; import { PanelOptions, PanelType, PanelDefaultOptions } from '../Panel'; -export function decodePanelOptionsFromQueryString(query: string): {key: string, options: PanelOptions}[] { +export function decodePanelOptionsFromQueryString(query: string): { key: string; options: PanelOptions }[] { if (query === '') { return []; } @@ -21,12 +21,14 @@ interface IncompletePanelOptions { stacked?: boolean; } -function parseParams(params: string[]): {key: string, options: PanelOptions}[] { - const sortedParams = params.filter((p) => { - return paramFormat.test(p); - }).sort(); +function parseParams(params: string[]): { key: string; options: PanelOptions }[] { + const sortedParams = params + .filter(p => { + return paramFormat.test(p); + }) + .sort(); - let panelOpts: {key: string, options: PanelOptions}[] = []; + const panelOpts: { key: string; options: PanelOptions }[] = []; let key = 0; let options: IncompletePanelOptions = {}; @@ -36,7 +38,7 @@ function parseParams(params: string[]): {key: string, options: PanelOptions}[] { if (!p.startsWith(prefix)) { panelOpts.push({ key: key.toString(), - options: {...PanelDefaultOptions, ...options}, + options: { ...PanelDefaultOptions, ...options }, }); options = {}; key++; @@ -46,17 +48,17 @@ function parseParams(params: string[]): {key: string, options: PanelOptions}[] { } panelOpts.push({ key: key.toString(), - options: {...PanelDefaultOptions, ...options}, + options: { ...PanelDefaultOptions, ...options }, }); return panelOpts; } function addParam(opts: IncompletePanelOptions, param: string): void { - let [ opt, val ] = param.split('='); + let [opt, val] = param.split('='); val = decodeURIComponent(val.replace(/\+/g, ' ')); - switch(opt) { + switch (opt) { case 'expr': opts.expr = val; break; @@ -97,29 +99,29 @@ function addParam(opts: IncompletePanelOptions, param: string): void { } } -export function encodePanelOptionsToQueryString(panels: {key: string, options: PanelOptions}[]): string { +export function encodePanelOptionsToQueryString(panels: { key: string; options: PanelOptions }[]): string { const queryParams: string[] = []; panels.forEach(p => { const prefix = 'g' + p.key + '.'; const o = p.options; - const panelParams: {[key: string]: string | undefined} = { - 'expr': o.expr, - 'tab': o.type === PanelType.Graph ? '0' : '1', - 'stacked': o.stacked ? '1' : '0', - 'range_input': formatRange(o.range), - 'end_input': o.endTime !== null ? formatTime(o.endTime) : undefined, - 'moment_input': o.endTime !== null ? formatTime(o.endTime) : undefined, - 'step_input': o.resolution !== null ? o.resolution.toString() : undefined, + const panelParams: { [key: string]: string | undefined } = { + expr: o.expr, + tab: o.type === PanelType.Graph ? '0' : '1', + stacked: o.stacked ? '1' : '0', + range_input: formatRange(o.range), + end_input: o.endTime !== null ? formatTime(o.endTime) : undefined, + moment_input: o.endTime !== null ? formatTime(o.endTime) : undefined, + step_input: o.resolution !== null ? o.resolution.toString() : undefined, }; - for (let o in panelParams) { + for (const o in panelParams) { const pp = panelParams[o]; if (pp !== undefined) { queryParams.push(prefix + o + '=' + encodeURIComponent(pp)); } } - }) + }); return '?' + queryParams.join('&'); } diff --git a/web/ui/react-app/yarn.lock b/web/ui/react-app/yarn.lock index cc01c6693e..d8758999ec 100644 --- a/web/ui/react-app/yarn.lock +++ b/web/ui/react-app/yarn.lock @@ -1464,6 +1464,17 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@2.x": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.5.0.tgz#101d96743ce3365b3223df73d641078c9b775903" + integrity sha512-ddrJZxp5ns1Lh5ofZQYk3P8RyvKfyz/VcRR4ZiJLHO/ljnQAO8YvTfj268+WJOOadn99mvDiqJA65+HAKoeSPA== + dependencies: + "@typescript-eslint/experimental-utils" "2.5.0" + eslint-utils "^1.4.2" + functional-red-black-tree "^1.0.1" + regexpp "^2.0.1" + tsutils "^3.17.1" + "@typescript-eslint/eslint-plugin@^2.2.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.4.0.tgz#aaf6b542ff75b78f4191a8bf1c519184817caa24" @@ -1484,6 +1495,25 @@ "@typescript-eslint/typescript-estree" "2.4.0" eslint-scope "^5.0.0" +"@typescript-eslint/experimental-utils@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.5.0.tgz#383a97ded9a7940e5053449f6d73995e782b8fb1" + integrity sha512-UgcQGE0GKJVChyRuN1CWqDW8Pnu7+mVst0aWrhiyuUD1J9c+h8woBdT4XddCvhcXDodTDVIfE3DzGHVjp7tUeQ== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "2.5.0" + eslint-scope "^5.0.0" + +"@typescript-eslint/parser@2.x": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.5.0.tgz#858030ddd808fbbe88e03f42e5971efaccb8218a" + integrity sha512-9UBMiAwIDWSl79UyogaBdj3hidzv6exjKUx60OuZuFnJf56tq/UMpdPcX09YmGqE8f4AnAueYtBxV8IcAT3jdQ== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "2.5.0" + "@typescript-eslint/typescript-estree" "2.5.0" + eslint-visitor-keys "^1.1.0" + "@typescript-eslint/parser@^2.2.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.4.0.tgz#fe43ed5fec14af03d3594fce2c3b7ec4c8df0243" @@ -1505,6 +1535,17 @@ lodash.unescape "4.0.1" semver "^6.3.0" +"@typescript-eslint/typescript-estree@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.5.0.tgz#40ada624d6217ef092a3a79ed30d947ad4f212ce" + integrity sha512-AXURyF8NcA3IsnbjNX1v9qbwa0dDoY9YPcKYR2utvMHoUcu3636zrz0gRWtVAyxbPCkhyKuGg6WZIyi2Fc79CA== + dependencies: + debug "^4.1.1" + glob "^7.1.4" + is-glob "^4.0.1" + lodash.unescape "4.0.1" + semver "^6.3.0" + "@webassemblyjs/ast@1.8.5": version "1.8.5" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" @@ -2033,7 +2074,7 @@ babel-code-frame@^6.22.0: esutils "^2.0.2" js-tokens "^3.0.2" -babel-eslint@10.0.3: +babel-eslint@10.0.3, babel-eslint@10.x: version "10.0.3" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a" integrity sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA== @@ -3842,6 +3883,13 @@ escodegen@^1.11.0, escodegen@^1.11.1, escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" +eslint-config-prettier@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.4.0.tgz#0a04f147e31d33c6c161b2dd0971418ac52d0477" + integrity sha512-YrKucoFdc7SEko5Sxe4r6ixqXPDP1tunGw91POeZTTRKItf/AMFYt/YLEQtZMkR2LVpAVhcAcZgcWpm1oGPW7w== + dependencies: + get-stdin "^6.0.0" + eslint-config-react-app@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-5.0.2.tgz#df40d73a1402986030680c040bbee520db5a32a4" @@ -3876,14 +3924,14 @@ eslint-module-utils@^2.4.0: debug "^2.6.8" pkg-dir "^2.0.0" -eslint-plugin-flowtype@3.13.0: +eslint-plugin-flowtype@3.13.0, eslint-plugin-flowtype@3.x: version "3.13.0" resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-3.13.0.tgz#e241ebd39c0ce519345a3f074ec1ebde4cf80f2c" integrity sha512-bhewp36P+t7cEV0b6OdmoRWJCBYRiHFlqPZAG1oS3SF+Y0LQkeDvFSM4oxoxvczD1OdONCXMlJfQFiWLcV9urw== dependencies: lodash "^4.17.15" -eslint-plugin-import@2.18.2: +eslint-plugin-import@2.18.2, eslint-plugin-import@2.x: version "2.18.2" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6" integrity sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ== @@ -3900,7 +3948,7 @@ eslint-plugin-import@2.18.2: read-pkg-up "^2.0.0" resolve "^1.11.0" -eslint-plugin-jsx-a11y@6.2.3: +eslint-plugin-jsx-a11y@6.2.3, eslint-plugin-jsx-a11y@6.x: version "6.2.3" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz#b872a09d5de51af70a97db1eea7dc933043708aa" integrity sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg== @@ -3915,7 +3963,14 @@ eslint-plugin-jsx-a11y@6.2.3: has "^1.0.3" jsx-ast-utils "^2.2.1" -eslint-plugin-react-hooks@^1.6.1: +eslint-plugin-prettier@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.1.tgz#507b8562410d02a03f0ddc949c616f877852f2ba" + integrity sha512-A+TZuHZ0KU0cnn56/9mfR7/KjUJ9QNVXUhwvRFSR7PGPe0zQR6PTkmyqg1AtUUEOzTqeRsUwyKFh0oVZKVCrtA== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-plugin-react-hooks@1.x, eslint-plugin-react-hooks@^1.6.1: version "1.7.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz#6210b6d5a37205f0b92858f895a4e827020a7d04" integrity sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA== @@ -3935,6 +3990,21 @@ eslint-plugin-react@7.14.3: prop-types "^15.7.2" resolve "^1.10.1" +eslint-plugin-react@7.x: + version "7.16.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz#9928e4f3e2122ed3ba6a5b56d0303ba3e41d8c09" + integrity sha512-GacBAATewhhptbK3/vTP09CbFrgUJmBSaaRcWdbQLFvUZy9yVcQxigBNHGPU/KE2AyHpzj3AWXpxoMTsIDiHug== + dependencies: + array-includes "^3.0.3" + doctrine "^2.1.0" + has "^1.0.3" + jsx-ast-utils "^2.2.1" + object.entries "^1.1.0" + object.fromentries "^2.0.0" + object.values "^1.1.0" + prop-types "^15.7.2" + resolve "^1.12.0" + eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -3963,7 +4033,7 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== -eslint@^6.1.0: +eslint@6.x, eslint@^6.1.0: version "6.5.1" resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.5.1.tgz#828e4c469697d43bb586144be152198b91e96ed6" integrity sha512-32h99BoLYStT1iq1v2P9uwpyznQ4M2jRiFB6acitKz52Gqn+vPaMDUTB1bYi1WN4Nquj2w+t+bimYUG83DC55A== @@ -4221,6 +4291,11 @@ fast-deep-equal@^2.0.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + fast-glob@^2.0.2: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" @@ -4599,6 +4674,11 @@ get-own-enumerable-property-symbols@^3.0.0: resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.1.tgz#6f7764f88ea11e0b514bd9bd860a132259992ca4" integrity sha512-09/VS4iek66Dh2bctjRkowueRJbY1JDGR1L/zRxO1Qk8Uxs6PnqaNSqalpizPT+CDjre3hnEsuzvhgomz9qYrA== +get-stdin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" + integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== + get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -8240,6 +8320,18 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^1.18.2: + version "1.18.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea" + integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw== + pretty-bytes@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2"