diff --git a/package-lock.json b/package-lock.json index 84d26cdf4c..0ed287932d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -946,6 +946,15 @@ "@types/sizzle": "*" } }, + "@types/moment-timezone": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@types/moment-timezone/-/moment-timezone-0.5.10.tgz", + "integrity": "sha512-sSFfubyYd0Z9C89/M5wZ+GJOWNTYTPhXAB2wrzmElJcQROyWTj0y1NCLSwYqovDYeFezlFE8+aopIyZTMsDVnA==", + "dev": true, + "requires": { + "moment": ">=2.14.0" + } + }, "@types/node": { "version": "11.9.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.3.tgz", diff --git a/package.json b/package.json index ec07e99a98..a03e43d869 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "not op_mini all" ], "devDependencies": { + "@types/moment-timezone": "^0.5.10", "@types/reactstrap": "^7.1.3" } } diff --git a/src/GraphControls.tsx b/src/GraphControls.tsx index ff7070e976..8f951e15b5 100644 --- a/src/GraphControls.tsx +++ b/src/GraphControls.tsx @@ -36,9 +36,9 @@ interface GraphControlsProps { resolution: number | null; stacked: boolean; - onChangeRange: (range: number | null) => void; - onChangeEndTime: (endTime: number | null) => void; - onChangeResolution: (resolution: number | null) => void; + onChangeRange: (range: number) => void; + onChangeEndTime: (endTime: number) => void; + onChangeResolution: (resolution: number) => void; onChangeStacking: (stacked: boolean) => void; } @@ -101,7 +101,7 @@ class GraphControls extends Component { if (range === null) { this.changeRangeInput(this.props.range); } else { - this.props.onChangeRange(this.parseRange(rangeText)); + this.props.onChangeRange(range); } } diff --git a/src/Panel.js b/src/Panel.tsx similarity index 79% rename from src/Panel.js rename to src/Panel.tsx index aeb8efff90..9f8cdceee7 100644 --- a/src/Panel.js +++ b/src/Panel.tsx @@ -20,28 +20,57 @@ import Graph from './Graph'; import DataTable from './DataTable'; import TimeInput from './TimeInput'; -class Panel extends Component { - constructor(props) { +interface PanelProps { + metricNames: string[]; + removePanel: () => void; + // TODO Put initial panel values here. +} + +interface PanelState { + expr: string; + type: 'graph' | 'table'; + range: number; + endTime: number | null; + resolution: number | null; + stacked: boolean; + data: any; // TODO: Define data. + lastQueryParams: { + startTime: number, + endTime: number, + resolution: number, + } | null, + loading: boolean; + error: string | null; + stats: null; // TODO: Stats. +} + +class Panel extends Component { + private abortInFlightFetch: (() => void) | null = null; + + constructor(props: PanelProps) { super(props); this.state = { expr: 'rate(node_cpu_seconds_total[1m])', - type: 'graph', // TODO enum? + type: 'graph', range: 3600, endTime: null, // This is in milliseconds. resolution: null, stacked: false, data: null, + lastQueryParams: null, + loading: false, error: null, stats: null, }; } - componentDidUpdate(prevProps, prevState) { - const needsRefresh = ['type', 'range', 'endTime', 'resolution'].some(v => { - return prevState[v] !== this.state[v]; - }) - if (needsRefresh) { + componentDidUpdate(prevProps: PanelProps, prevState: PanelState) { + if (prevState.type !== this.state.type || + prevState.range !== this.state.range || + prevState.endTime !== this.state.endTime || + prevState.resolution !== this.state.resolution) { + if (prevState.type !== this.state.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 @@ -56,7 +85,7 @@ class Panel extends Component { this.executeQuery(); } - executeQuery = ()=> { + executeQuery = (): void => { if (this.state.expr === '') { return; } @@ -70,12 +99,12 @@ class Panel extends Component { this.abortInFlightFetch = () => abortController.abort(); this.setState({loading: true}); - let endTime = this.getEndTime() / 1000; - let startTime = endTime - this.state.range; - let resolution = this.state.resolution || Math.max(Math.floor(this.state.range / 250), 1); + const endTime = this.getEndTime().valueOf() / 1000; // TODO: shouldn'T valueof only work when it's a moment? + const startTime = endTime - this.state.range; + const resolution = this.state.resolution || Math.max(Math.floor(this.state.range / 250), 1); - let url = new URL('http://demo.robustperception.io:9090/');//window.location.href); - let params = { + const url = new URL('http://demo.robustperception.io:9090/');//window.location.href); + const params: {[key: string]: string} = { 'query': this.state.expr, }; @@ -100,7 +129,7 @@ class Panel extends Component { } Object.keys(params).forEach(key => url.searchParams.append(key, params[key])) - fetch(url, {cache: 'no-store', signal: abortController.signal}) + fetch(url.toString(), {cache: 'no-store', signal: abortController.signal}) .then(resp => resp.json()) .then(json => { if (json.status !== 'success') { @@ -131,26 +160,26 @@ class Panel extends Component { }); } - handleExpressionChange = (expr) => { + handleExpressionChange = (expr: string): void => { this.setState({expr: expr}); } - handleChangeRange = (range) => { + handleChangeRange = (range: number): void => { this.setState({range: range}); } - getEndTime = () => { + getEndTime = (): number | moment.Moment => { if (this.state.endTime === null) { return moment(); } return this.state.endTime; } - handleChangeEndTime = (endTime) => { + handleChangeEndTime = (endTime: number) => { this.setState({endTime: endTime}); } - handleChangeResolution = (resolution) => { + handleChangeResolution = (resolution: number) => { // TODO: Where should we validate domain model constraints? In the parent's // change handler like here, or in the calling component? if (resolution > 0) { @@ -194,7 +223,7 @@ class Panel extends Component { // self.submitQuery(); // }; - handleChangeStacking = (stacked) => { + handleChangeStacking = (stacked: boolean) => { this.setState({stacked: stacked}); } @@ -259,7 +288,7 @@ class Panel extends Component { {this.state.type === 'table' && <> -
+