From 5d7958988fc76798bc6d339430c9ff49968e0b51 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Mon, 18 Jun 2018 16:57:56 -0700 Subject: [PATCH] Typescriptify Status Page and children --- .../containers/chronograf/AllUsersPage.tsx | 2 +- ui/src/alerts/apis/index.ts | 2 +- ui/src/shared/actions/timeSeries.ts | 45 ++- ui/src/shared/components/Dygraph.tsx | 6 +- ui/src/shared/constants/index.tsx | 21 ++ ui/src/shared/data/timeRanges.ts | 8 +- ui/src/sources/containers/ManageSources.tsx | 4 +- ui/src/sources/containers/SourcePage.js | 278 -------------- ui/src/sources/containers/SourcePage.tsx | 348 +++++++++--------- ui/src/status/actions/index.js | 49 --- ui/src/status/actions/index.ts | 3 +- ui/src/status/apis/index.ts | 5 +- ui/src/status/components/GettingStarted.js | 101 ----- ui/src/status/components/JSONFeedReader.js | 61 --- ui/src/status/components/NewsFeed.js | 92 ----- ui/src/status/constants/actionTypes.js | 6 - ui/src/status/constants/index.js | 1 - ui/src/status/containers/StatusPage.js | 113 ------ ui/src/status/containers/StatusPage.tsx | 19 +- ui/src/status/fixtures.ts | 105 +++--- ui/src/status/reducers/JSONFeed.js | 43 --- ui/src/status/reducers/index.js | 7 - ui/src/status/reducers/ui.js | 36 -- ui/src/types/dashboard.ts | 3 +- ui/src/types/index.ts | 2 + 25 files changed, 294 insertions(+), 1066 deletions(-) delete mode 100644 ui/src/sources/containers/SourcePage.js delete mode 100644 ui/src/status/actions/index.js delete mode 100644 ui/src/status/components/GettingStarted.js delete mode 100644 ui/src/status/components/JSONFeedReader.js delete mode 100644 ui/src/status/components/NewsFeed.js delete mode 100644 ui/src/status/constants/actionTypes.js delete mode 100644 ui/src/status/constants/index.js delete mode 100644 ui/src/status/containers/StatusPage.js delete mode 100644 ui/src/status/reducers/JSONFeed.js delete mode 100644 ui/src/status/reducers/index.js delete mode 100644 ui/src/status/reducers/ui.js diff --git a/ui/src/admin/containers/chronograf/AllUsersPage.tsx b/ui/src/admin/containers/chronograf/AllUsersPage.tsx index 8f42680e5..c2d514132 100644 --- a/ui/src/admin/containers/chronograf/AllUsersPage.tsx +++ b/ui/src/admin/containers/chronograf/AllUsersPage.tsx @@ -48,7 +48,7 @@ interface State { @ErrorHandling export class AllUsersPage extends PureComponent { - constructor(props) { + constructor(props: Props) { super(props) this.state = { diff --git a/ui/src/alerts/apis/index.ts b/ui/src/alerts/apis/index.ts index 4222e7fe7..4d8ae0833 100644 --- a/ui/src/alerts/apis/index.ts +++ b/ui/src/alerts/apis/index.ts @@ -1,5 +1,5 @@ import {proxy} from 'src/utils/queryUrlGenerator' -import {TimeRange} from '../../types' +import {TimeRange} from 'src/types' export const getAlerts = ( source: string, diff --git a/ui/src/shared/actions/timeSeries.ts b/ui/src/shared/actions/timeSeries.ts index 7f5c852ee..8b1f2fc69 100644 --- a/ui/src/shared/actions/timeSeries.ts +++ b/ui/src/shared/actions/timeSeries.ts @@ -3,15 +3,38 @@ import {noop} from 'src/shared/actions/app' import _ from 'lodash' import {errorThrown} from 'src/shared/actions/errors' +import {TimeSeriesResponse} from 'src/types/series' -export const handleLoading = (query, editQueryStatus) => { +interface Query { + host: string | string[] + text: string + id: string + database?: string + db?: string + rp?: string +} + +interface Payload { + source: string + query: Query + tempVars: any[] + db?: string + rp?: string + resolution?: number +} + +export const handleLoading = (query: Query, editQueryStatus) => { editQueryStatus(query.id, { loading: true, }) } // {results: [{}]} -export const handleSuccess = (data, query, editQueryStatus) => { +export const handleSuccess = ( + data: TimeSeriesResponse, + query: Query, + editQueryStatus +) => { const {results} = data const error = _.get(results, ['0', 'error'], false) const series = _.get(results, ['0', 'series'], false) @@ -51,24 +74,6 @@ export const handleError = (error, query, editQueryStatus) => { }) } -interface Query { - host: string | string[] - text: string - id: string - database?: string - db?: string - rp?: string -} - -interface Payload { - source: string - query: Query - tempVars: any[] - db?: string - rp?: string - resolution?: number -} - export const fetchTimeSeriesAsync = async ( {source, db, rp, query, tempVars, resolution}: Payload, editQueryStatus = noop diff --git a/ui/src/shared/components/Dygraph.tsx b/ui/src/shared/components/Dygraph.tsx index 852802527..a9a275d89 100644 --- a/ui/src/shared/components/Dygraph.tsx +++ b/ui/src/shared/components/Dygraph.tsx @@ -175,8 +175,10 @@ class Dygraph extends Component { } public componentWillUnmount() { - this.dygraph.destroy() - delete this.dygraph + if (this.dygraph) { + this.dygraph.destroy() + delete this.dygraph + } } public shouldComponentUpdate(nextProps: Props, nextState: State) { diff --git a/ui/src/shared/constants/index.tsx b/ui/src/shared/constants/index.tsx index 647848f3b..bc93c1a82 100644 --- a/ui/src/shared/constants/index.tsx +++ b/ui/src/shared/constants/index.tsx @@ -427,7 +427,23 @@ export const DYGRAPH_CONTAINER_H_MARGIN = 16 export const DYGRAPH_CONTAINER_V_MARGIN = 8 export const DYGRAPH_CONTAINER_XLABEL_MARGIN = 20 +export const DEFAULT_SOURCE_LINKS = { + self: '', + kapacitors: '', + proxy: '', + queries: '', + write: '', + permissions: '', + users: '', + roles: '', + databases: '', + annotations: '', + health: '', + services: '', +} + export const DEFAULT_SOURCE = { + id: '', url: 'http://localhost:8086', name: 'Influx 1', username: '', @@ -436,6 +452,11 @@ export const DEFAULT_SOURCE = { telegraf: 'telegraf', insecureSkipVerify: false, metaUrl: '', + organization: '', + role: '', + defaultRP: '', + links: DEFAULT_SOURCE_LINKS, + type: '', } export const defaultIntervalValue = '333' diff --git a/ui/src/shared/data/timeRanges.ts b/ui/src/shared/data/timeRanges.ts index f711a56eb..0b6f91b47 100644 --- a/ui/src/shared/data/timeRanges.ts +++ b/ui/src/shared/data/timeRanges.ts @@ -6,6 +6,8 @@ interface TimeRangeOption extends TimeRange { menuOption: string } +const nowminus30d = 'now() - 30d' + export const timeRanges: TimeRangeOption[] = [ { defaultGroupBy: '10s', @@ -75,7 +77,7 @@ export const timeRanges: TimeRangeOption[] = [ defaultGroupBy: '6h', seconds: 2592000, inputValue: 'Past 30d', - lower: 'now() - 30d', + lower: nowminus30d, upper: null, menuOption: 'Past 30d', }, @@ -89,3 +91,7 @@ export const defaultTimeRange = { seconds: 900, format: FORMAT_INFLUXQL, } + +export const STATUS_PAGE_TIME_RANGE = timeRanges.find( + tr => tr.lower === nowminus30d +) diff --git a/ui/src/sources/containers/ManageSources.tsx b/ui/src/sources/containers/ManageSources.tsx index d6336d47a..e1aa15679 100644 --- a/ui/src/sources/containers/ManageSources.tsx +++ b/ui/src/sources/containers/ManageSources.tsx @@ -14,12 +14,12 @@ import { notifySourceDeleteFailed, } from 'src/shared/copy/notifications' -import {Source, NotificationFunc} from 'src/types' +import {Source, Notification} from 'src/types' interface Props { source: Source sources: Source[] - notify: (n: NotificationFunc) => void + notify: (n: Notification) => void deleteKapacitor: actions.DeleteKapacitorAsync fetchKapacitors: actions.FetchKapacitorsAsync removeAndLoadSources: actions.RemoveAndLoadSources diff --git a/ui/src/sources/containers/SourcePage.js b/ui/src/sources/containers/SourcePage.js deleted file mode 100644 index 7d44ace13..000000000 --- a/ui/src/sources/containers/SourcePage.js +++ /dev/null @@ -1,278 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import {withRouter} from 'react-router' -import _ from 'lodash' -import {getSource} from 'shared/apis' -import {createSource, updateSource} from 'shared/apis' -import { - addSource as addSourceAction, - updateSource as updateSourceAction, -} from 'shared/actions/sources' -import {notify as notifyAction} from 'shared/actions/notifications' -import {connect} from 'react-redux' -import {bindActionCreators} from 'redux' - -import Notifications from 'shared/components/Notifications' -import SourceForm from 'src/sources/components/SourceForm' -import FancyScrollbar from 'shared/components/FancyScrollbar' -import SourceIndicator from 'shared/components/SourceIndicator' -import {DEFAULT_SOURCE} from 'shared/constants' -const initialPath = '/sources/new' - -import { - notifyErrorConnectingToSource, - notifySourceCreationSucceeded, - notifySourceCreationFailed, - notifySourceUdpated, - notifySourceUdpateFailed, -} from 'shared/copy/notifications' -import {ErrorHandling} from 'src/shared/decorators/errors' - -@ErrorHandling -class SourcePage extends Component { - constructor(props) { - super(props) - - this.state = { - isLoading: true, - source: DEFAULT_SOURCE, - editMode: props.params.id !== undefined, - isInitialSource: props.router.location.pathname === initialPath, - } - } - - componentDidMount() { - const {editMode} = this.state - const {params, notify} = this.props - - if (!editMode) { - return this.setState({isLoading: false}) - } - - getSource(params.id) - .then(({data: source}) => { - this.setState({ - source: {...DEFAULT_SOURCE, ...source}, - isLoading: false, - }) - }) - .catch(error => { - notify(notifyErrorConnectingToSource(this._parseError(error))) - this.setState({isLoading: false}) - }) - } - - handleInputChange = e => { - let val = e.target.value - const name = e.target.name - - if (e.target.type === 'checkbox') { - val = e.target.checked - } - - this.setState(prevState => { - const source = { - ...prevState.source, - [name]: val, - } - - return {...prevState, source} - }) - } - - handleBlurSourceURL = () => { - const {source, editMode} = this.state - if (editMode) { - this.setState(this._normalizeSource) - return - } - - if (!source.url) { - return - } - - this.setState(this._normalizeSource, this._createSourceOnBlur) - } - - handleSubmit = e => { - e.preventDefault() - const {isCreated, editMode} = this.state - const isNewSource = !editMode - - if (!isCreated && isNewSource) { - return this.setState(this._normalizeSource, this._createSource) - } - - this.setState(this._normalizeSource, this._updateSource) - } - - gotoPurgatory = () => { - const {router} = this.props - router.push('/purgatory') - } - - _normalizeSource({source}) { - const url = source.url.trim() - if (source.url.startsWith('http')) { - return {source: {...source, url}} - } - return {source: {...source, url: `http://${url}`}} - } - - _createSourceOnBlur = () => { - const {source} = this.state - // if there is a type on source it has already been created - if (source.type) { - return - } - createSource(source) - .then(({data: sourceFromServer}) => { - this.props.addSource(sourceFromServer) - this.setState({ - source: {...DEFAULT_SOURCE, ...sourceFromServer}, - isCreated: true, - }) - }) - .catch(err => { - // dont want to flash this until they submit - const error = this._parseError(err) - console.error('Error creating InfluxDB connection: ', error) - }) - } - - _createSource = () => { - const {source} = this.state - const {notify} = this.props - createSource(source) - .then(({data: sourceFromServer}) => { - this.props.addSource(sourceFromServer) - this._redirect(sourceFromServer) - notify(notifySourceCreationSucceeded(source.name)) - }) - .catch(error => { - notify(notifySourceCreationFailed(source.name, this._parseError(error))) - }) - } - - _updateSource = () => { - const {source} = this.state - const {notify} = this.props - updateSource(source) - .then(({data: sourceFromServer}) => { - this.props.updateSource(sourceFromServer) - this._redirect(sourceFromServer) - notify(notifySourceUdpated(source.name)) - }) - .catch(error => { - notify(notifySourceUdpateFailed(source.name, this._parseError(error))) - }) - } - - _redirect = source => { - const {isInitialSource} = this.state - const {params, router} = this.props - - if (isInitialSource) { - return this._redirectToApp(source) - } - - router.push(`/sources/${params.sourceID}/manage-sources`) - } - - _redirectToApp = source => { - const {location, router} = this.props - const {redirectPath} = location.query - - if (!redirectPath) { - return router.push(`/sources/${source.id}/hosts`) - } - - const fixedPath = redirectPath.replace( - /\/sources\/[^/]*/, - `/sources/${source.id}` - ) - return router.push(fixedPath) - } - - _parseError = error => { - return _.get(error, ['data', 'message'], error) - } - - render() { - const {isLoading, source, editMode, isInitialSource} = this.state - - if (isLoading) { - return
- } - - return ( -
- -
-
-
-
-

- {editMode - ? 'Configure InfluxDB Connection' - : 'Add a New InfluxDB Connection'} -

-
- {isInitialSource ? null : ( -
- -
- )} -
-
-
- -
-
-
-
- -
-
-
-
-
-
- ) - } -} - -const {func, shape, string} = PropTypes - -SourcePage.propTypes = { - params: shape({ - id: string, - sourceID: string, - }), - router: shape({ - push: func.isRequired, - }).isRequired, - location: shape({ - query: shape({ - redirectPath: string, - }).isRequired, - }).isRequired, - notify: func.isRequired, - addSource: func.isRequired, - updateSource: func.isRequired, -} - -const mapDispatchToProps = dispatch => ({ - notify: bindActionCreators(notifyAction, dispatch), - addSource: bindActionCreators(addSourceAction, dispatch), - updateSource: bindActionCreators(updateSourceAction, dispatch), -}) -export default connect(null, mapDispatchToProps)(withRouter(SourcePage)) diff --git a/ui/src/sources/containers/SourcePage.tsx b/ui/src/sources/containers/SourcePage.tsx index 9212b9acf..c0a00dd28 100644 --- a/ui/src/sources/containers/SourcePage.tsx +++ b/ui/src/sources/containers/SourcePage.tsx @@ -1,7 +1,6 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import {withRouter} from 'react-router' -import _ from 'lodash' +import React, {Component, ChangeEvent, FormEvent} from 'react' +import {withRouter, InjectedRouter} from 'react-router' +import {Location} from 'history' import {getSource} from 'src/shared/apis' import {createSource, updateSource} from 'src/shared/apis' import { @@ -27,45 +26,47 @@ import { notifySourceUdpateFailed, } from 'src/shared/copy/notifications' import {ErrorHandling} from 'src/shared/decorators/errors' -import {Source} from 'src/types' +import {Source, Notification, NotificationFunc} from 'src/types' +import {getDeep} from 'src/utils/wrappers' + +interface Params { + id: string + hash: string + sourceID: string +} interface Props { - params: shape({ - id: string, - sourceID: string, - }), - router: shape({ - push: func.isRequired, - }).isRequired, - location: shape({ - query: shape({ - redirectPath: string, - }).isRequired, - }).isRequired, - notify: func.isRequired, - addSource: func.isRequired, - updateSource: func.isRequired,} + location: Location + router: InjectedRouter + params: Params + notify: (notification: Notification | NotificationFunc) => void + addSource: (s: Source) => void + updateSource: (s: Source) => void +} -interface State { +interface State { isLoading: boolean + isCreated: boolean source: Source editMode: boolean - isInitialSource: boolean} - + isInitialSource: boolean +} + @ErrorHandling class SourcePage extends Component { - constructor(props:Props) { + constructor(props: Props) { super(props) this.state = { isLoading: true, + isCreated: false, source: DEFAULT_SOURCE, editMode: props.params.id !== undefined, - isInitialSource: props.router.location.pathname === initialPath, + isInitialSource: props.location.pathname === initialPath, } } - componentDidMount() { + public componentDidMount() { const {editMode} = this.state const {params, notify} = this.props @@ -81,148 +82,12 @@ class SourcePage extends Component { }) }) .catch(error => { - notify(notifyErrorConnectingToSource(this._parseError(error))) + notify(notifyErrorConnectingToSource(this.parseError(error))) this.setState({isLoading: false}) }) } - handleInputChange = e => { - let val = e.target.value - const name = e.target.name - - if (e.target.type === 'checkbox') { - val = e.target.checked - } - - this.setState(prevState => { - const source = { - ...prevState.source, - [name]: val, - } - - return {...prevState, source} - }) - } - - handleBlurSourceURL = () => { - const {source, editMode} = this.state - if (editMode) { - this.setState(this._normalizeSource) - return - } - - if (!source.url) { - return - } - - this.setState(this._normalizeSource, this._createSourceOnBlur) - } - - handleSubmit = e => { - e.preventDefault() - const {isCreated, editMode} = this.state - const isNewSource = !editMode - - if (!isCreated && isNewSource) { - return this.setState(this._normalizeSource, this._createSource) - } - - this.setState(this._normalizeSource, this._updateSource) - } - - gotoPurgatory = () => { - const {router} = this.props - router.push('/purgatory') - } - - _normalizeSource({source}) { - const url = source.url.trim() - if (source.url.startsWith('http')) { - return {source: {...source, url}} - } - return {source: {...source, url: `http://${url}`}} - } - - _createSourceOnBlur = () => { - const {source} = this.state - // if there is a type on source it has already been created - if (source.type) { - return - } - createSource(source) - .then(({data: sourceFromServer}) => { - this.props.addSource(sourceFromServer) - this.setState({ - source: {...DEFAULT_SOURCE, ...sourceFromServer}, - isCreated: true, - }) - }) - .catch(err => { - // dont want to flash this until they submit - const error = this._parseError(err) - console.error('Error creating InfluxDB connection: ', error) - }) - } - - _createSource = () => { - const {source} = this.state - const {notify} = this.props - createSource(source) - .then(({data: sourceFromServer}) => { - this.props.addSource(sourceFromServer) - this._redirect(sourceFromServer) - notify(notifySourceCreationSucceeded(source.name)) - }) - .catch(error => { - notify(notifySourceCreationFailed(source.name, this._parseError(error))) - }) - } - - _updateSource = () => { - const {source} = this.state - const {notify} = this.props - updateSource(source) - .then(({data: sourceFromServer}) => { - this.props.updateSource(sourceFromServer) - this._redirect(sourceFromServer) - notify(notifySourceUdpated(source.name)) - }) - .catch(error => { - notify(notifySourceUdpateFailed(source.name, this._parseError(error))) - }) - } - - _redirect = source => { - const {isInitialSource} = this.state - const {params, router} = this.props - - if (isInitialSource) { - return this._redirectToApp(source) - } - - router.push(`/sources/${params.sourceID}/manage-sources`) - } - - _redirectToApp = source => { - const {location, router} = this.props - const {redirectPath} = location.query - - if (!redirectPath) { - return router.push(`/sources/${source.id}/hosts`) - } - - const fixedPath = redirectPath.replace( - /\/sources\/[^/]*/, - `/sources/${source.id}` - ) - return router.push(fixedPath) - } - - _parseError = error => { - return _.get(error, ['data', 'message'], error) - } - - render() { + public render() { const {isLoading, source, editMode, isInitialSource} = this.state if (isLoading) { @@ -272,26 +137,143 @@ class SourcePage extends Component {
) } -} -const {func, shape, string} = PropTypes + private handleInputChange = (e: ChangeEvent) => { + let val: string | boolean = e.target.value + const name = e.target.name -SourcePage.propTypes = { - params: shape({ - id: string, - sourceID: string, - }), - router: shape({ - push: func.isRequired, - }).isRequired, - location: shape({ - query: shape({ - redirectPath: string, - }).isRequired, - }).isRequired, - notify: func.isRequired, - addSource: func.isRequired, - updateSource: func.isRequired, + if (e.target.type === 'checkbox') { + val = e.target.checked + } + + this.setState(prevState => { + const source = { + ...prevState.source, + [name]: val, + } + + return {...prevState, source} + }) + } + + private handleBlurSourceURL = () => { + const {source, editMode} = this.state + if (editMode) { + this.setState(this.normalizeSource) + return + } + + if (!source.url) { + return + } + + this.setState(this.normalizeSource, this.createSourceOnBlur) + } + + private handleSubmit = (e: FormEvent) => { + e.preventDefault() + const {isCreated, editMode} = this.state + const isNewSource = !editMode + + if (!isCreated && isNewSource) { + return this.setState(this.normalizeSource, this.createSource) + } + + this.setState(this.normalizeSource, this.updateSource) + } + + private gotoPurgatory = () => { + const {router} = this.props + router.push('/purgatory') + } + + private normalizeSource() { + const {source} = this.state + const url = source.url.trim() + if (source.url.startsWith('http')) { + return {source: {...source, url}} + } + return {source: {...source, url: `http://${url}`}} + } + + private createSourceOnBlur = () => { + const {source} = this.state + // if there is a type on source it has already been created + if (source.type) { + return + } + createSource(source) + .then(({data: sourceFromServer}) => { + this.props.addSource(sourceFromServer) + this.setState({ + source: {...DEFAULT_SOURCE, ...sourceFromServer}, + isCreated: true, + }) + }) + .catch(err => { + // dont want to flash this until they submit + const error = this.parseError(err) + console.error('Error creating InfluxDB connection: ', error) + }) + } + + private createSource = () => { + const {source} = this.state + const {notify} = this.props + createSource(source) + .then(({data: sourceFromServer}) => { + this.props.addSource(sourceFromServer) + this.redirect(sourceFromServer) + notify(notifySourceCreationSucceeded(source.name)) + }) + .catch(error => { + notify(notifySourceCreationFailed(source.name, this.parseError(error))) + }) + } + + private updateSource = () => { + const {source} = this.state + const {notify} = this.props + updateSource(source) + .then(({data: sourceFromServer}) => { + this.props.updateSource(sourceFromServer) + this.redirect(sourceFromServer) + notify(notifySourceUdpated(source.name)) + }) + .catch(error => { + notify(notifySourceUdpateFailed(source.name, this.parseError(error))) + }) + } + + private redirect = (source: Source) => { + const {isInitialSource} = this.state + const {params, router} = this.props + + if (isInitialSource) { + return this.redirectToApp(source) + } + + router.push(`/sources/${params.sourceID}/manage-sources`) + } + + private redirectToApp = (source: Source) => { + const {location, router} = this.props + const {redirectPath} = location.query + + if (!redirectPath) { + return router.push(`/sources/${source.id}/hosts`) + } + + const fixedPath = redirectPath.replace( + /\/sources\/[^/]*/, + `/sources/${source.id}` + ) + return router.push(fixedPath) + } + + private parseError = error => { + return getDeep(error, 'data.message', '') + } } const mapDispatchToProps = dispatch => ({ @@ -299,4 +281,4 @@ const mapDispatchToProps = dispatch => ({ addSource: bindActionCreators(addSourceAction, dispatch), updateSource: bindActionCreators(updateSourceAction, dispatch), }) -export default connect(null, mapDispatchToProps)(withRouter(SourcePage)) +export default withRouter(connect(null, mapDispatchToProps)(SourcePage)) diff --git a/ui/src/status/actions/index.js b/ui/src/status/actions/index.js deleted file mode 100644 index 0aa93af11..000000000 --- a/ui/src/status/actions/index.js +++ /dev/null @@ -1,49 +0,0 @@ -// he is a library for safely encoding and decoding HTML Entities -import he from 'he' - -import {fetchJSONFeed as fetchJSONFeedAJAX} from 'src/status/apis' - -import {notify} from 'src/shared/actions/notifications' -import {notifyJSONFeedFailed} from 'src/shared/copy/notifications' - -import * as actionTypes from 'src/status/constants/actionTypes' - -const fetchJSONFeedRequested = () => ({ - type: actionTypes.FETCH_JSON_FEED_REQUESTED, -}) - -const fetchJSONFeedCompleted = data => ({ - type: actionTypes.FETCH_JSON_FEED_COMPLETED, - payload: {data}, -}) - -const fetchJSONFeedFailed = () => ({ - type: actionTypes.FETCH_JSON_FEED_FAILED, -}) - -export const fetchJSONFeedAsync = url => async dispatch => { - dispatch(fetchJSONFeedRequested()) - try { - const {data} = await fetchJSONFeedAJAX(url) - // data could be from a webpage, and thus would be HTML - if (typeof data === 'string' || !data) { - dispatch(fetchJSONFeedFailed()) - } else { - // decode HTML entities from response text - const decodedData = { - ...data, - items: data.items.map(item => { - item.title = he.decode(item.title) - item.content_text = he.decode(item.content_text) - return item - }), - } - - dispatch(fetchJSONFeedCompleted(decodedData)) - } - } catch (error) { - console.error(error) - dispatch(fetchJSONFeedFailed()) - dispatch(notify(notifyJSONFeedFailed(url))) - } -} diff --git a/ui/src/status/actions/index.ts b/ui/src/status/actions/index.ts index 40b8cb3f8..b8e37416e 100644 --- a/ui/src/status/actions/index.ts +++ b/ui/src/status/actions/index.ts @@ -7,6 +7,7 @@ import {notify} from 'src/shared/actions/notifications' import {notifyJSONFeedFailed} from 'src/shared/copy/notifications' import {JSONFeedData} from 'src/types' +import {AxiosResponse} from 'axios' export enum ActionTypes { FETCH_JSON_FEED_REQUESTED = 'FETCH_JSON_FEED_REQUESTED', @@ -52,7 +53,7 @@ export const fetchJSONFeedAsync = (url: string) => async ( ): Promise => { dispatch(fetchJSONFeedRequested()) try { - const {data} = await fetchJSONFeedAJAX(url) + const {data} = (await fetchJSONFeedAJAX(url)) as AxiosResponse // data could be from a webpage, and thus would be HTML if (typeof data === 'string' || !data) { dispatch(fetchJSONFeedFailed()) diff --git a/ui/src/status/apis/index.ts b/ui/src/status/apis/index.ts index 7bb8642f1..1ac19fa75 100644 --- a/ui/src/status/apis/index.ts +++ b/ui/src/status/apis/index.ts @@ -1,9 +1,10 @@ import AJAX from 'src/utils/ajax' +import {JSONFeedData} from 'src/types' const excludeBasepath = true // don't prefix route of external link with basepath/ -export const fetchJSONFeed = url => - AJAX( +export const fetchJSONFeed = (url: string) => + AJAX( { method: 'GET', url, diff --git a/ui/src/status/components/GettingStarted.js b/ui/src/status/components/GettingStarted.js deleted file mode 100644 index a3d1d179e..000000000 --- a/ui/src/status/components/GettingStarted.js +++ /dev/null @@ -1,101 +0,0 @@ -import React, {Component} from 'react' - -import FancyScrollbar from 'shared/components/FancyScrollbar' -import {ErrorHandling} from 'src/shared/decorators/errors' - -@ErrorHandling -class GettingStarted extends Component { - constructor(props) { - super(props) - } - - render() { - return ( - -
-
-
- Welcome to Chronograf! -
-

Follow the links below to explore Chronograf’s features.

-
-
-

- Install the TICK Stack -
Save some time and use this handy tool to install the rest - of the stack: -

-

- - TICK Sandbox - -

-
- -
-

- Questions & Comments -

-

- If you have any product feedback please open a GitHub issue and - we'll take a look. For any questions or other issues try posting - on our  - - Community Forum - . -

-
-
-
- ) - } -} - -export default GettingStarted diff --git a/ui/src/status/components/JSONFeedReader.js b/ui/src/status/components/JSONFeedReader.js deleted file mode 100644 index a5adbf60c..000000000 --- a/ui/src/status/components/JSONFeedReader.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import moment from 'moment' - -const JSONFeedReader = ({data}) => - data && data.items ? ( -
- {data.items - ? data.items.map( - ({ - id, - date_published: datePublished, - url, - title, - author: {name}, - image, - content_text: contentText, - }) => ( -
-
- {`${moment(datePublished).format('MMM DD')}`} -
-
- -
{title}
-
- by {name} -
-
- {image ? : null} -

{contentText}

-
-
- ) - ) - : null} -
- ) : null - -const {arrayOf, shape, string} = PropTypes - -JSONFeedReader.propTypes = { - data: shape({ - items: arrayOf( - shape({ - author: shape({ - name: string.isRequired, - }).isRequired, - content_text: string.isRequired, - date_published: string.isRequired, - id: string.isRequired, - image: string, - title: string.isRequired, - url: string.isRequired, - }) - ), - }).isRequired, -} - -export default JSONFeedReader diff --git a/ui/src/status/components/NewsFeed.js b/ui/src/status/components/NewsFeed.js deleted file mode 100644 index 8aa84fb77..000000000 --- a/ui/src/status/components/NewsFeed.js +++ /dev/null @@ -1,92 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import {connect} from 'react-redux' -import {bindActionCreators} from 'redux' - -import {fetchJSONFeedAsync} from 'src/status/actions' - -import FancyScrollbar from 'shared/components/FancyScrollbar' -import JSONFeedReader from 'src/status/components/JSONFeedReader' -import {ErrorHandling} from 'src/shared/decorators/errors' - -@ErrorHandling -class NewsFeed extends Component { - constructor(props) { - super(props) - } - - // TODO: implement shouldComponentUpdate based on fetching conditions - - render() { - const {hasCompletedFetchOnce, isFetching, isFailed, data} = this.props - - if (!hasCompletedFetchOnce) { - return isFailed ? ( -
-

Failed to load News Feed

-
- ) : ( - // TODO: Factor this out of here and AutoRefresh -
-
-
- ) - } - - return ( - - {isFetching ? ( - // TODO: Factor this out of here and AutoRefresh -
-
-
-
-
- ) : null} - {isFailed ? ( -
-

Failed to refresh News Feed

-
- ) : null} - - - ) - } - - // TODO: implement interval polling a la AutoRefresh - componentDidMount() { - const {statusFeedURL, fetchJSONFeed} = this.props - - fetchJSONFeed(statusFeedURL) - } -} - -const {bool, func, shape, string} = PropTypes - -NewsFeed.propTypes = { - hasCompletedFetchOnce: bool.isRequired, - isFetching: bool.isRequired, - isFailed: bool.isRequired, - data: shape(), - fetchJSONFeed: func.isRequired, - statusFeedURL: string, -} - -const mapStateToProps = ({ - links: { - external: {statusFeed: statusFeedURL}, - }, - JSONFeed: {hasCompletedFetchOnce, isFetching, isFailed, data}, -}) => ({ - hasCompletedFetchOnce, - isFetching, - isFailed, - data, - statusFeedURL, -}) - -const mapDispatchToProps = dispatch => ({ - fetchJSONFeed: bindActionCreators(fetchJSONFeedAsync, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(NewsFeed) diff --git a/ui/src/status/constants/actionTypes.js b/ui/src/status/constants/actionTypes.js deleted file mode 100644 index d49669bb2..000000000 --- a/ui/src/status/constants/actionTypes.js +++ /dev/null @@ -1,6 +0,0 @@ -export const SET_STATUS_PAGE_AUTOREFRESH = 'SET_STATUS_PAGE_AUTOREFRESH' -export const SET_STATUS_PAGE_TIME_RANGE = 'SET_STATUS_PAGE_TIME_RANGE' - -export const FETCH_JSON_FEED_REQUESTED = 'FETCH_JSON_FEED_REQUESTED' -export const FETCH_JSON_FEED_COMPLETED = 'FETCH_JSON_FEED_COMPLETED' -export const FETCH_JSON_FEED_FAILED = 'FETCH_JSON_FEED_FAILED' diff --git a/ui/src/status/constants/index.js b/ui/src/status/constants/index.js deleted file mode 100644 index 01421d10c..000000000 --- a/ui/src/status/constants/index.js +++ /dev/null @@ -1 +0,0 @@ -export const RECENT_ALERTS_LIMIT = 30 diff --git a/ui/src/status/containers/StatusPage.js b/ui/src/status/containers/StatusPage.js deleted file mode 100644 index 157e7a658..000000000 --- a/ui/src/status/containers/StatusPage.js +++ /dev/null @@ -1,113 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import {connect} from 'react-redux' - -import SourceIndicator from 'shared/components/SourceIndicator' -import FancyScrollbar from 'shared/components/FancyScrollbar' -import LayoutRenderer from 'shared/components/LayoutRenderer' - -import {fixtureStatusPageCells} from 'src/status/fixtures' -import {ErrorHandling} from 'src/shared/decorators/errors' -import { - TEMP_VAR_DASHBOARD_TIME, - TEMP_VAR_UPPER_DASHBOARD_TIME, -} from 'src/shared/constants' - -@ErrorHandling -class StatusPage extends Component { - constructor(props) { - super(props) - - this.state = { - cells: fixtureStatusPageCells, - } - } - - render() { - const {source, autoRefresh, timeRange} = this.props - const {cells} = this.state - - const dashboardTime = { - id: 'dashtime', - tempVar: TEMP_VAR_DASHBOARD_TIME, - type: 'constant', - values: [ - { - value: timeRange.lower, - type: 'constant', - selected: true, - }, - ], - } - - const upperDashboardTime = { - id: 'upperdashtime', - tempVar: TEMP_VAR_UPPER_DASHBOARD_TIME, - type: 'constant', - values: [ - { - value: 'now()', - type: 'constant', - selected: true, - }, - ], - } - - const templates = [dashboardTime, upperDashboardTime] - - return ( -
-
-
-
-

Status

-
-
- -
-
-
- -
- {cells.length ? ( - - ) : ( - Loading Status Page... - )} -
-
-
- ) - } -} - -const {number, shape, string} = PropTypes - -StatusPage.propTypes = { - source: shape({ - name: string.isRequired, - links: shape({ - proxy: string.isRequired, - }).isRequired, - }).isRequired, - autoRefresh: number.isRequired, - timeRange: shape({ - lower: string.isRequired, - }).isRequired, -} - -const mapStateToProps = ({statusUI: {autoRefresh, timeRange}}) => ({ - autoRefresh, - timeRange, -}) - -export default connect(mapStateToProps, null)(StatusPage) diff --git a/ui/src/status/containers/StatusPage.tsx b/ui/src/status/containers/StatusPage.tsx index 348f69b1e..6492ee48b 100644 --- a/ui/src/status/containers/StatusPage.tsx +++ b/ui/src/status/containers/StatusPage.tsx @@ -1,9 +1,10 @@ import React, {Component} from 'react' -import {connect} from 'react-redux' import SourceIndicator from 'src/shared/components/SourceIndicator' 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 {fixtureStatusPageCells} from 'src/status/fixtures' import {ErrorHandling} from 'src/shared/decorators/errors' @@ -11,7 +12,7 @@ import { TEMP_VAR_DASHBOARD_TIME, TEMP_VAR_UPPER_DASHBOARD_TIME, } from 'src/shared/constants' -import {Source, TimeRange, Cell} from 'src/types' +import {Source, Cell} from 'src/types' interface State { cells: Cell[] @@ -19,10 +20,11 @@ interface State { interface Props { source: Source - autoRefresh: number - timeRange: TimeRange } +const autoRefresh = AUTOREFRESH_DEFAULT +const timeRange = STATUS_PAGE_TIME_RANGE + @ErrorHandling class StatusPage extends Component { constructor(props: Props) { @@ -34,7 +36,7 @@ class StatusPage extends Component { } public render() { - const {source, autoRefresh, timeRange} = this.props + const {source} = this.props const {cells} = this.state const dashboardTime = { @@ -100,9 +102,4 @@ class StatusPage extends Component { } } -const mstp = ({statusUI: {autoRefresh, timeRange}}) => ({ - autoRefresh, - timeRange, -}) - -export default connect(mstp, null)(StatusPage) +export default StatusPage diff --git a/ui/src/status/fixtures.ts b/ui/src/status/fixtures.ts index 67bdcbf92..5bf0b171d 100644 --- a/ui/src/status/fixtures.ts +++ b/ui/src/status/fixtures.ts @@ -1,9 +1,40 @@ import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes' import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants' +import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants/index' +import {DEFAULT_AXIS} from 'src/dashboards/constants/cellEditor' +import {Cell, CellQuery} from 'src/types' +import {CellType} from 'src/types/dashboard' -export const fixtureStatusPageCells = [ +const emptyQuery: CellQuery = { + query: '', + source: '', + queryConfig: { + database: '', + measurement: '', + retentionPolicy: '', + fields: [], + tags: {}, + groupBy: {}, + areTagsAccepted: false, + rawText: null, + range: null, + }, +} + +const emptyAxes = { + axes: { + x: DEFAULT_AXIS, + y: DEFAULT_AXIS, + y2: DEFAULT_AXIS, + }, +} + +export const fixtureStatusPageCells: Cell[] = [ { + ...NEW_DEFAULT_DASHBOARD_CELL, + ...emptyAxes, i: 'alerts-bar-graph', + type: CellType.Bar, isWidget: false, x: 0, y: 0, @@ -15,7 +46,7 @@ export const fixtureStatusPageCells = [ queries: [ { query: `SELECT count("value") AS "count_value" FROM "chronograf"."autogen"."alerts" WHERE time > ${TEMP_VAR_DASHBOARD_TIME} GROUP BY time(1d)`, - label: 'Events', + source: '', queryConfig: { database: 'chronograf', measurement: 'alerts', @@ -44,90 +75,56 @@ export const fixtureStatusPageCells = [ }, }, ], - type: 'bar', links: { self: '/chronograf/v1/status/23/cells/c-bar-graphs-fly', }, }, { + ...NEW_DEFAULT_DASHBOARD_CELL, + ...emptyAxes, i: 'recent-alerts', + type: CellType.Alerts, isWidget: true, name: 'Alerts – Last 30 Days', - type: 'alerts', x: 0, y: 5, w: 6.5, h: 6, legend: {}, - queries: [ - { - query: '', - queryConfig: { - database: '', - measurement: '', - retentionPolicy: '', - fields: [], - tags: {}, - groupBy: {}, - areTagsAccepted: false, - rawText: null, - range: null, - }, - }, - ], + queries: [emptyQuery], + colors: DEFAULT_LINE_COLORS, + links: {self: ''}, }, { + ...NEW_DEFAULT_DASHBOARD_CELL, + ...emptyAxes, i: 'news-feed', + type: CellType.News, isWidget: true, name: 'News Feed', - type: 'news', x: 6.5, y: 5, w: 3, h: 6, legend: {}, - queries: [ - { - query: '', - queryConfig: { - database: '', - measurement: '', - retentionPolicy: '', - fields: [], - tags: {}, - groupBy: {}, - areTagsAccepted: false, - rawText: null, - range: null, - }, - }, - ], + queries: [emptyQuery], + colors: DEFAULT_LINE_COLORS, + links: {self: ''}, }, { + ...NEW_DEFAULT_DASHBOARD_CELL, + ...emptyAxes, i: 'getting-started', + type: CellType.Guide, isWidget: true, name: 'Getting Started', - type: 'guide', x: 9.5, y: 5, w: 2.5, h: 6, legend: {}, - queries: [ - { - query: '', - queryConfig: { - database: '', - measurement: '', - retentionPolicy: '', - fields: [], - tags: {}, - groupBy: {}, - areTagsAccepted: false, - rawText: null, - range: null, - }, - }, - ], + queries: [emptyQuery], + colors: DEFAULT_LINE_COLORS, + links: {self: ''}, }, ] diff --git a/ui/src/status/reducers/JSONFeed.js b/ui/src/status/reducers/JSONFeed.js deleted file mode 100644 index 76d52d959..000000000 --- a/ui/src/status/reducers/JSONFeed.js +++ /dev/null @@ -1,43 +0,0 @@ -import * as actionTypes from 'src/status/constants/actionTypes' - -const initialState = { - hasCompletedFetchOnce: false, - isFetching: false, - isFailed: false, - data: null, -} - -const JSONFeedReducer = (state = initialState, action) => { - switch (action.type) { - case actionTypes.FETCH_JSON_FEED_REQUESTED: { - return {...state, isFetching: true, isFailed: false} - } - - case actionTypes.FETCH_JSON_FEED_COMPLETED: { - const {data} = action.payload - - return { - ...state, - hasCompletedFetchOnce: true, - isFetching: false, - isFailed: false, - data, - } - } - - case actionTypes.FETCH_JSON_FEED_FAILED: { - return { - ...state, - isFetching: false, - isFailed: true, - data: null, - } - } - - default: { - return state - } - } -} - -export default JSONFeedReducer diff --git a/ui/src/status/reducers/index.js b/ui/src/status/reducers/index.js deleted file mode 100644 index d0941c654..000000000 --- a/ui/src/status/reducers/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import statusUI from './ui' -import JSONFeed from './JSONFeed' - -export default { - statusUI, - JSONFeed, -} diff --git a/ui/src/status/reducers/ui.js b/ui/src/status/reducers/ui.js deleted file mode 100644 index a2a8ac7cd..000000000 --- a/ui/src/status/reducers/ui.js +++ /dev/null @@ -1,36 +0,0 @@ -import {AUTOREFRESH_DEFAULT} from 'shared/constants' -import {timeRanges} from 'shared/data/timeRanges' - -import * as actionTypes from 'src/status/constants/actionTypes' - -const {lower, upper} = timeRanges.find(tr => tr.lower === 'now() - 30d') - -const initialState = { - autoRefresh: AUTOREFRESH_DEFAULT, - timeRange: {lower, upper}, -} - -const statusUI = (state = initialState, action) => { - switch (action.type) { - case actionTypes.SET_STATUS_PAGE_AUTOREFRESH: { - const {milliseconds} = action.payload - - return { - ...state, - autoRefresh: milliseconds, - } - } - - case actionTypes.SET_STATUS_PAGE_TIME_RANGE: { - const {timeRange} = action.payload - - return {...state, timeRange} - } - - default: { - return state - } - } -} - -export default statusUI diff --git a/ui/src/types/dashboard.ts b/ui/src/types/dashboard.ts index 0f3a31ea2..cd2141cb1 100644 --- a/ui/src/types/dashboard.ts +++ b/ui/src/types/dashboard.ts @@ -3,12 +3,12 @@ import {ColorString} from 'src/types/colors' import {Template} from 'src/types/tempVars' export interface Axis { - bounds: [string, string] label: string prefix: string suffix: string base: string scale: string + bounds?: [string, string] } export type TimeSeriesValue = string | number | null | undefined @@ -76,6 +76,7 @@ export interface Cell { decimalPlaces: DecimalPlaces links: CellLinks legend: Legend + isWidget?: boolean } export enum CellType { diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 60da481c9..d2443a520 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -40,6 +40,7 @@ import { DygraphClass, DygraphData, } from './dygraphs' +import {JSONFeedData} from './status' export { Me, @@ -96,4 +97,5 @@ export { SchemaFilter, RemoteDataState, URLQueryParams, + JSONFeedData, }