diff --git a/ui/spec/shared/reducers/annotationsSpec.js b/ui/spec/shared/reducers/annotationsSpec.js
index 36a64d77b2..f82a7739a2 100644
--- a/ui/spec/shared/reducers/annotationsSpec.js
+++ b/ui/spec/shared/reducers/annotationsSpec.js
@@ -27,36 +27,43 @@ const a2 = {
text: 'you have no swoggels',
}
+const state = {
+ mode: null,
+ annotations: [],
+}
+
describe.only('Shared.Reducers.annotations', () => {
it('can load the annotations', () => {
- const state = []
const expected = [{time: '0', duration: ''}]
const actual = reducer(state, loadAnnotations(expected))
- expect(actual).to.deep.equal(expected)
+ expect(actual.annotations).to.deep.equal(expected)
})
it('can update an annotation', () => {
- const state = [a1]
const expected = [{...a1, time: ''}]
- const actual = reducer(state, updateAnnotation(expected[0]))
+ const actual = reducer(
+ {...state, annotations: [a1]},
+ updateAnnotation(expected[0])
+ )
- expect(actual).to.deep.equal(expected)
+ expect(actual.annotations).to.deep.equal(expected)
})
it('can delete an annotation', () => {
- const state = [a1, a2]
const expected = [a2]
- const actual = reducer(state, deleteAnnotation(a1))
+ const actual = reducer(
+ {...state, annotations: [a1, a2]},
+ deleteAnnotation(a1)
+ )
- expect(actual).to.deep.equal(expected)
+ expect(actual.annotations).to.deep.equal(expected)
})
it('can add an annotation', () => {
- const state = []
const expected = [{...a1, id: DEFAULT_ANNOTATION_ID}]
const actual = reducer(state, addAnnotation(a1))
- expect(actual).to.deep.equal(expected)
+ expect(actual.annotations).to.deep.equal(expected)
})
})
diff --git a/ui/src/dashboards/components/Dashboard.js b/ui/src/dashboards/components/Dashboard.js
index 41b31710d2..f562fd483e 100644
--- a/ui/src/dashboards/components/Dashboard.js
+++ b/ui/src/dashboards/components/Dashboard.js
@@ -23,6 +23,7 @@ const Dashboard = ({
onSummonOverlayTechnologies,
onSelectTemplate,
showTemplateControlBar,
+ onStartAddingAnnotation,
}) => {
const cells = dashboard.cells.map(cell => {
const dashboardCell = {...cell}
@@ -63,6 +64,7 @@ const Dashboard = ({
onDeleteCell={onDeleteCell}
onPositionChange={onPositionChange}
templates={templatesIncludingDashTime}
+ onStartAddingAnnotation={onStartAddingAnnotation}
onSummonOverlayTechnologies={onSummonOverlayTechnologies}
/>
:
@@ -119,6 +121,7 @@ Dashboard.propTypes = {
onSelectTemplate: func.isRequired,
showTemplateControlBar: bool,
onZoom: func,
+ onStartAddingAnnotation: func.isRequired,
}
export default Dashboard
diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js
index c04b8cc725..e4376e5349 100644
--- a/ui/src/dashboards/containers/DashboardPage.js
+++ b/ui/src/dashboards/containers/DashboardPage.js
@@ -25,6 +25,7 @@ import {
setAutoRefresh,
templateControlBarVisibilityToggled as templateControlBarVisibilityToggledAction,
} from 'shared/actions/app'
+import {addingAnnotation} from 'shared/actions/annotations'
import {presentationButtonDispatcher} from 'shared/dispatchers'
const FORMAT_INFLUXQL = 'influxql'
@@ -248,6 +249,7 @@ class DashboardPage extends Component {
handleChooseAutoRefresh,
handleClickPresentationButton,
params: {sourceID, dashboardID},
+ handleStartAddingAnnotation,
} = this.props
const low = zoomedLower ? zoomedLower : lower
@@ -387,6 +389,7 @@ class DashboardPage extends Component {
showTemplateControlBar={showTemplateControlBar}
onOpenTemplateManager={this.handleOpenTemplateManager}
templatesIncludingDashTime={templatesIncludingDashTime}
+ onStartAddingAnnotation={handleStartAddingAnnotation}
onSummonOverlayTechnologies={this.handleSummonOverlayTechnologies}
/>
: null}
@@ -467,6 +470,7 @@ DashboardPage.propTypes = {
isUsingAuth: bool.isRequired,
router: shape().isRequired,
notify: func.isRequired,
+ handleStartAddingAnnotation: func.isRequired,
}
const mapStateToProps = (state, {params: {dashboardID}}) => {
@@ -516,6 +520,7 @@ const mapDispatchToProps = dispatch => ({
dashboardActions: bindActionCreators(dashboardActionCreators, dispatch),
errorThrown: bindActionCreators(errorThrownAction, dispatch),
notify: bindActionCreators(publishNotification, dispatch),
+ handleStartAddingAnnotation: bindActionCreators(addingAnnotation, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(
diff --git a/ui/src/shared/actions/annotations.js b/ui/src/shared/actions/annotations.js
index c72511dc9b..833e8d0785 100644
--- a/ui/src/shared/actions/annotations.js
+++ b/ui/src/shared/actions/annotations.js
@@ -1,3 +1,11 @@
+export const addingAnnotation = () => ({
+ type: 'ADDING_ANNOTATION',
+})
+
+export const addingAnnotationSuccess = () => ({
+ type: 'ADDING_ANNOTATION_SUCCESS',
+})
+
export const loadAnnotations = annotations => ({
type: 'LOAD_ANNOTATIONS',
payload: {
diff --git a/ui/src/shared/annotations/helpers.js b/ui/src/shared/annotations/helpers.js
index f132debfb1..1eb0c12083 100644
--- a/ui/src/shared/annotations/helpers.js
+++ b/ui/src/shared/annotations/helpers.js
@@ -1,3 +1,15 @@
+export const ADDING = 'adding'
+export const EDITING = 'editing'
+
+export const TEMP_ANNOTATION = {
+ id: 'tempAnnotation',
+ group: '',
+ name: 'New Annotation',
+ time: '',
+ duration: '',
+ text: '',
+}
+
export const getAnnotations = (graph, annotations = []) => {
if (!graph) {
return []
diff --git a/ui/src/shared/components/Annotations.js b/ui/src/shared/components/Annotations.js
index 19b922292e..271e70f3be 100644
--- a/ui/src/shared/components/Annotations.js
+++ b/ui/src/shared/components/Annotations.js
@@ -4,11 +4,15 @@ import {bindActionCreators} from 'redux'
import Annotation from 'src/shared/components/Annotation'
import AnnotationWindow from 'src/shared/components/AnnotationWindow'
+import NewAnnotation from 'src/shared/components/NewAnnotation'
+
+import {ADDING} from 'src/shared/annotations/helpers'
import {
addAnnotation,
updateAnnotation,
deleteAnnotation,
+ addingAnnotationSuccess,
} from 'src/shared/actions/annotations'
import {getAnnotations} from 'src/shared/annotations/helpers'
@@ -23,7 +27,13 @@ class Annotations extends Component {
render() {
const {dygraph} = this.state
- const {mode, handleUpdateAnnotation, handleDeleteAnnotation} = this.props
+ const {
+ mode,
+ handleUpdateAnnotation,
+ handleDeleteAnnotation,
+ handleAddAnnotation,
+ handleAddingAnnotationSuccess,
+ } = this.props
if (!dygraph) {
return null
@@ -33,6 +43,12 @@ class Annotations extends Component {
return (
+ {mode === ADDING &&
+
}
{annotations.map(a =>
({
+const mapStateToProps = ({annotations: {annotations, mode}}) => ({
annotations,
+ mode,
})
const mapDispatchToProps = dispatch => ({
+ handleAddingAnnotationSuccess: bindActionCreators(
+ addingAnnotationSuccess,
+ dispatch
+ ),
handleAddAnnotation: bindActionCreators(addAnnotation, dispatch),
handleUpdateAnnotation: bindActionCreators(updateAnnotation, dispatch),
handleDeleteAnnotation: bindActionCreators(deleteAnnotation, dispatch),
diff --git a/ui/src/shared/components/Dygraph.js b/ui/src/shared/components/Dygraph.js
index f0af965f0d..f776987a64 100644
--- a/ui/src/shared/components/Dygraph.js
+++ b/ui/src/shared/components/Dygraph.js
@@ -295,17 +295,12 @@ export default class Dygraph extends Component {
handleAnnotationsRef = ref => (this.annotationsRef = ref)
render() {
- const {annotationMode} = this.props
const {isHidden} = this.state
return (
-
+
{this.dygraph &&
- annotationMode !== 'adding' &&
{cell.isWidget
@@ -85,7 +85,7 @@ const Layout = (
autoRefresh={autoRefresh}
synchronizer={synchronizer}
manualRefresh={manualRefresh}
- annotationMode={annotationMode}
+ onStopAddAnnotation={onStopAddAnnotation}
grabDataForDownload={grabDataForDownload}
resizeCoords={resizeCoords}
queries={buildQueriesForLayouts(
diff --git a/ui/src/shared/components/LayoutCell.js b/ui/src/shared/components/LayoutCell.js
index 6499deaf71..c049bd3f6f 100644
--- a/ui/src/shared/components/LayoutCell.js
+++ b/ui/src/shared/components/LayoutCell.js
@@ -35,7 +35,7 @@ class LayoutCell extends Component {
children,
isEditable,
celldata,
- onStartAddAnnotation,
+ onStartAddingAnnotation,
} = this.props
const queries = _.get(cell, ['queries'], [])
@@ -52,7 +52,7 @@ class LayoutCell extends Component {
onEdit={this.handleSummonOverlay}
handleClickOutside={this.closeMenu}
onCSVDownload={this.handleCSVDownload}
- onStartAddAnnotation={onStartAddAnnotation}
+ onStartAddingAnnotation={onStartAddingAnnotation}
/>
@@ -91,7 +91,7 @@ LayoutCell.propTypes = {
isEditable: bool,
onCancelEditCell: func,
celldata: arrayOf(shape()),
- onStartAddAnnotation: func.isRequired,
+ onStartAddingAnnotation: func.isRequired,
}
export default LayoutCell
diff --git a/ui/src/shared/components/LayoutCellMenu.js b/ui/src/shared/components/LayoutCellMenu.js
index f8deef579b..253b2ef8a5 100644
--- a/ui/src/shared/components/LayoutCellMenu.js
+++ b/ui/src/shared/components/LayoutCellMenu.js
@@ -23,7 +23,7 @@ class LayoutCellMenu extends Component {
isEditable,
dataExists,
onCSVDownload,
- onStartAddAnnotation,
+ onStartAddingAnnotation,
} = this.props
return (
@@ -45,7 +45,7 @@ class LayoutCellMenu extends Component {
icon="pencil"
menuOptions={[
{text: 'Queries', action: onEdit(cell)},
- {text: 'Add Annotation', action: onStartAddAnnotation},
+ {text: 'Add Annotation', action: onStartAddingAnnotation},
]}
informParent={this.handleToggleSubMenu}
/>
@@ -79,7 +79,7 @@ LayoutCellMenu.propTypes = {
dataExists: bool,
onCSVDownload: func,
queries: arrayOf(shape()),
- onStartAddAnnotation: func.isRequired,
+ onStartAddingAnnotation: func.isRequired,
}
export default LayoutCellMenu
diff --git a/ui/src/shared/components/LayoutRenderer.js b/ui/src/shared/components/LayoutRenderer.js
index 49e54c32f1..6e59d2fe5c 100644
--- a/ui/src/shared/components/LayoutRenderer.js
+++ b/ui/src/shared/components/LayoutRenderer.js
@@ -26,14 +26,9 @@ class LayoutRenderer extends Component {
this.state = {
rowHeight: this.calculateRowHeight(),
resizeCoords: null,
- annotationMode: null,
}
}
- handleStartAddAnnotation = () => {
- this.setState({annotationMode: 'adding'})
- }
-
handleLayoutChange = layout => {
if (!this.props.onPositionChange) {
return
@@ -86,10 +81,11 @@ class LayoutRenderer extends Component {
onDeleteCell,
synchronizer,
onCancelEditCell,
+ onStartAddingAnnotation,
onSummonOverlayTechnologies,
} = this.props
- const {rowHeight, resizeCoords, annotationMode} = this.state
+ const {rowHeight, resizeCoords} = this.state
const isDashboard = !!this.props.onPositionChange
return (
@@ -139,9 +135,9 @@ class LayoutRenderer extends Component {
onDeleteCell={onDeleteCell}
synchronizer={synchronizer}
manualRefresh={manualRefresh}
- annotationMode={annotationMode}
onCancelEditCell={onCancelEditCell}
- onStartAddAnnotation={this.handleStartAddAnnotation}
+ onStopAddAnnotation={this.handleStopAddAnnotation}
+ onStartAddingAnnotation={onStartAddingAnnotation}
onSummonOverlayTechnologies={onSummonOverlayTechnologies}
/>
@@ -199,6 +195,7 @@ LayoutRenderer.propTypes = {
onCancelEditCell: func,
onZoom: func,
sources: arrayOf(shape({})),
+ onStartAddingAnnotation: func.isRequired,
}
export default LayoutRenderer
diff --git a/ui/src/shared/components/LineGraph.js b/ui/src/shared/components/LineGraph.js
index 754f0e6c62..a64feff7a4 100644
--- a/ui/src/shared/components/LineGraph.js
+++ b/ui/src/shared/components/LineGraph.js
@@ -51,7 +51,6 @@ class LineGraph extends Component {
isRefreshing,
setResolution,
isGraphFilled,
- annotationMode,
showSingleStat,
displayOptions,
underlayCallback,
@@ -112,7 +111,6 @@ class LineGraph extends Component {
setResolution={setResolution}
overrideLineColors={lineColors}
containerStyle={containerStyle}
- annotationMode={annotationMode}
isGraphFilled={showSingleStat ? false : isGraphFilled}
/>
{showSingleStat
@@ -180,7 +178,6 @@ LineGraph.propTypes = {
resizeCoords: shape(),
queries: arrayOf(shape({}).isRequired).isRequired,
data: arrayOf(shape({}).isRequired).isRequired,
- annotationMode: string,
}
export default LineGraph
diff --git a/ui/src/shared/components/NewAnnotation.js b/ui/src/shared/components/NewAnnotation.js
index 67a02c886f..842785c012 100644
--- a/ui/src/shared/components/NewAnnotation.js
+++ b/ui/src/shared/components/NewAnnotation.js
@@ -38,12 +38,13 @@ const prompterContainerStyle = isMouseHovering => {
const prompterStyle = isMouseHovering => {
return {
- padding: '16px',
+ padding: '16px 32px',
textAlign: 'center',
- backgroundColor: 'rgba(255,0,0,0.2)',
+ backgroundColor: 'rgba(255,0,0,0.7)',
color: '#fff',
- borderRadius: '4px',
- fontSize: '16px',
+ borderRadius: '5px',
+ fontSize: '17px',
+ lineHeight: '30px',
fontWeight: '400',
opacity: isMouseHovering ? '0' : '1',
transition: 'opacity 0.25s ease',
@@ -53,7 +54,7 @@ const prompterStyle = isMouseHovering => {
class NewAnnotation extends Component {
state = {
xPos: null,
- isMouseHovering: false,
+ isMouseHovering: true,
}
handleMouseEnter = () => {
@@ -67,38 +68,39 @@ class NewAnnotation extends Component {
const wrapperRect = this.wrapper.getBoundingClientRect()
const trueGraphX = e.pageX - wrapperRect.left
- console.log('mouseMove')
this.setState({xPos: trueGraphX})
}
handleMouseLeave = () => {
- console.log('mouseLeave')
this.setState({xPos: null, isMouseHovering: false})
}
handleClick = () => {
- const {onAddAnnotation, dygraph} = this.props
+ const {onAddAnnotation, onAddingAnnotationSuccess, dygraph} = this.props
const {xPos} = this.state
- const time = dygraph.toDataXCoord(xPos)
+ const time = `${dygraph.toDataXCoord(xPos)}`
const annotation = {
- id: 'newannotationid',
+ id: 'newannotationid', // TODO generate real ID
group: '',
name: 'New Annotation',
time,
duration: '',
text: '',
}
- console.log(annotation)
- onAddAnnotation(annotation)
+
this.setState({xPos: null, isMouseHovering: false})
+ onAddingAnnotationSuccess()
+ onAddAnnotation(annotation)
}
render() {
- // const {dygraph} = this.props
+ const {dygraph} = this.props
const {xPos, isMouseHovering} = this.state
+ const timestamp = `${new Date(dygraph.toDataXCoord(xPos))}`
+
return (
- Click to add Annotation
+ Click to create Annotation
- Drag to add Range
+ Drag to create Range
-
+
+ Create Annotation at:
+
+ {timestamp}
+
)
@@ -132,7 +138,7 @@ const {func, shape} = PropTypes
NewAnnotation.propTypes = {
dygraph: shape({}).isRequired,
onAddAnnotation: func.isRequired,
- onCancelAddAnnotation: func.isRequired,
+ onAddingAnnotationSuccess: func.isRequired,
}
export default NewAnnotation
diff --git a/ui/src/shared/components/RefreshingGraph.js b/ui/src/shared/components/RefreshingGraph.js
index 8b02e26850..05b7917de3 100644
--- a/ui/src/shared/components/RefreshingGraph.js
+++ b/ui/src/shared/components/RefreshingGraph.js
@@ -22,7 +22,6 @@ const RefreshingGraph = ({
cellHeight,
autoRefresh,
resizerTopHeight,
- annotationMode,
manualRefresh, // when changed, re-mounts the component
synchronizer,
resizeCoords,
@@ -86,7 +85,6 @@ const RefreshingGraph = ({
isBarGraph={type === 'bar'}
synchronizer={synchronizer}
resizeCoords={resizeCoords}
- annotationMode={annotationMode}
displayOptions={displayOptions}
editQueryStatus={editQueryStatus}
grabDataForDownload={grabDataForDownload}
@@ -98,7 +96,6 @@ const RefreshingGraph = ({
const {arrayOf, func, number, shape, string} = PropTypes
RefreshingGraph.propTypes = {
- annotationMode: string,
timeRange: shape({
lower: string.isRequired,
}),
diff --git a/ui/src/shared/reducers/annotations.js b/ui/src/shared/reducers/annotations.js
index eaf49974d5..e997df621f 100644
--- a/ui/src/shared/reducers/annotations.js
+++ b/ui/src/shared/reducers/annotations.js
@@ -1,48 +1,76 @@
import {DEFAULT_ANNOTATION_ID} from 'src/shared/constants/annotations'
+import {ADDING, TEMP_ANNOTATION} from 'src/shared/annotations/helpers'
-const initialState = [
- {
- id: '0',
- group: '',
- name: 'anno1',
- time: '1515716169000',
- duration: '33600000', // 1 hour
- text: 'you have no swoggels',
- },
- {
- id: '1',
- group: '',
- name: 'anno2',
- time: '1515772377000',
- duration: '',
- text: 'another annotation',
- },
-]
+const initialState = {
+ mode: null,
+ annotations: [
+ {
+ id: '0',
+ group: '',
+ name: 'anno1',
+ time: '1515716169000',
+ duration: '33600000', // 1 hour
+ text: 'you have no swoggels',
+ },
+ {
+ id: '1',
+ group: '',
+ name: 'anno2',
+ time: '1515772377000',
+ duration: '',
+ text: 'another annotation',
+ },
+ ],
+}
const annotationsReducer = (state = initialState, action) => {
switch (action.type) {
+ case 'ADDING_ANNOTATION': {
+ return {
+ ...state,
+ mode: ADDING,
+ annotations: [...state.annotations, TEMP_ANNOTATION],
+ }
+ }
+
+ case 'ADDING_ANNOTATION_SUCCESS': {
+ const annotations = state.annotations.filter(
+ a => a.id !== TEMP_ANNOTATION.id
+ )
+
+ return {...state, mode: null, annotations}
+ }
+
case 'LOAD_ANNOTATIONS': {
- return action.payload.annotations
+ const {annotations} = action.payload
+
+ return {...state, annotations}
}
case 'UPDATE_ANNOTATION': {
const {annotation} = action.payload
- const newState = state.map(a => (a.id === annotation.id ? annotation : a))
+ const annotations = state.annotations.map(
+ a => (a.id === annotation.id ? annotation : a)
+ )
- return newState
+ return {...state, annotations}
}
case 'DELETE_ANNOTATION': {
const {annotation} = action.payload
- const newState = state.filter(a => a.id !== annotation.id)
+ const annotations = state.annotations.filter(a => a.id !== annotation.id)
- return newState
+ return {...state, annotations}
}
case 'ADD_ANNOTATION': {
const {annotation} = action.payload
+ const annotations = [
+ ...state.annotations,
+ {...annotation, id: DEFAULT_ANNOTATION_ID},
+ ]
- return [...state, {...annotation, id: DEFAULT_ANNOTATION_ID}]
+ return {...state, annotations}
}
}