Merge pull request #3614 from influxdata/bugfix/multiple-legends

Bugfix/multiple legends
pull/3612/head^2
Deniz Kusefoglu 2018-06-11 12:52:35 -07:00 committed by GitHub
commit 61d2645c59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 143 additions and 327 deletions

View File

@ -37,19 +37,19 @@ import {makeQueryForTemplate} from 'src/dashboards/utils/templateVariableQueryGe
import parsers from 'src/shared/parsing'
import {getDeep} from 'src/utils/wrappers'
import {Dashboard, TimeRange, Cell, Query, Source, Template} from 'src/types'
import {Dashboard, TimeRange, Cell, Source, Template} from 'src/types'
interface LoadDashboardsAction {
type: 'LOAD_DASHBOARDS'
payload: {
dashboards: Dashboard[]
dashboardID: string
dashboardID: number
}
}
export const loadDashboards = (
dashboards: Dashboard[],
dashboardID?: string
dashboardID?: number
): LoadDashboardsAction => ({
type: 'LOAD_DASHBOARDS',
payload: {
@ -61,12 +61,12 @@ export const loadDashboards = (
interface LoadDeafaultDashTimeV1Action {
type: 'ADD_DASHBOARD_TIME_V1'
payload: {
dashboardID: string
dashboardID: number
}
}
export const loadDeafaultDashTimeV1 = (
dashboardID: string
dashboardID: number
): LoadDeafaultDashTimeV1Action => ({
type: 'ADD_DASHBOARD_TIME_V1',
payload: {
@ -77,13 +77,13 @@ export const loadDeafaultDashTimeV1 = (
interface AddDashTimeV1Action {
type: 'ADD_DASHBOARD_TIME_V1'
payload: {
dashboardID: string
dashboardID: number
timeRange: TimeRange
}
}
export const addDashTimeV1 = (
dashboardID: string,
dashboardID: number,
timeRange: TimeRange
): AddDashTimeV1Action => ({
type: 'ADD_DASHBOARD_TIME_V1',
@ -96,13 +96,13 @@ export const addDashTimeV1 = (
interface SetDashTimeV1Action {
type: 'SET_DASHBOARD_TIME_V1'
payload: {
dashboardID: string
dashboardID: number
timeRange: TimeRange
}
}
export const setDashTimeV1 = (
dashboardID: string,
dashboardID: number,
timeRange: TimeRange
): SetDashTimeV1Action => ({
type: 'SET_DASHBOARD_TIME_V1',
@ -192,25 +192,6 @@ export const deleteDashboardFailed = (
},
})
interface UpdateDashboardCellsAction {
type: 'UPDATE_DASHBOARD_CELLS'
payload: {
dashboard: Dashboard
cells: Cell[]
}
}
export const updateDashboardCells = (
dashboard: Dashboard,
cells: Cell[]
): UpdateDashboardCellsAction => ({
type: 'UPDATE_DASHBOARD_CELLS',
payload: {
dashboard,
cells,
},
})
interface SyncDashboardCellAction {
type: 'SYNC_DASHBOARD_CELL'
payload: {
@ -249,79 +230,6 @@ export const addDashboardCell = (
},
})
interface EditDashboardCellAction {
type: 'EDIT_DASHBOARD_CELL'
payload: {
dashboard: Dashboard
x: number
y: number
isEditing: boolean
}
}
export const editDashboardCell = (
dashboard: Dashboard,
x: number,
y: number,
isEditing: boolean
): EditDashboardCellAction => ({
type: 'EDIT_DASHBOARD_CELL',
// x and y coords are used as a alternative to cell ids, which are not
// universally unique, and cannot be because React depends on a
// quasi-predictable ID for keys. Since cells cannot overlap, coordinates act
// as a suitable id
payload: {
dashboard,
x, // x-coord of the cell to be edited
y, // y-coord of the cell to be edited
isEditing,
},
})
interface CancelEditCellAction {
type: 'CANCEL_EDIT_CELL'
payload: {
dashboardID: string
cellID: string
}
}
export const cancelEditCell = (
dashboardID: string,
cellID: string
): CancelEditCellAction => ({
type: 'CANCEL_EDIT_CELL',
payload: {
dashboardID,
cellID,
},
})
interface RenameDashboardCellAction {
type: 'RENAME_DASHBOARD_CELL'
payload: {
dashboard: Dashboard
x: number
y: number
name: string
}
}
export const renameDashboardCell = (
dashboard: Dashboard,
x: number,
y: number,
name: string
): RenameDashboardCellAction => ({
type: 'RENAME_DASHBOARD_CELL',
payload: {
dashboard,
x, // x-coord of the cell to be renamed
y, // y-coord of the cell to be renamed
name,
},
})
interface DeleteDashboardCellAction {
type: 'DELETE_DASHBOARD_CELL'
payload: {
@ -363,14 +271,14 @@ export const editCellQueryStatus = (
interface TemplateVariableSelectedAction {
type: 'TEMPLATE_VARIABLE_SELECTED'
payload: {
dashboardID: string
dashboardID: number
templateID: string
values: any[]
}
}
export const templateVariableSelected = (
dashboardID: string,
dashboardID: number,
templateID: string,
values
): TemplateVariableSelectedAction => ({
@ -382,18 +290,11 @@ export const templateVariableSelected = (
},
})
interface TemplateVariablesSelectedByNameAction {
type: 'TEMPLATE_VARIABLES_SELECTED_BY_NAME'
payload: {
dashboardID: string
query: Query
}
}
// This is limited in typing as it will be changed soon
export const templateVariablesSelectedByName = (
dashboardID: string,
query: Query
): TemplateVariablesSelectedByNameAction => ({
dashboardID: number,
query: any
) => ({
type: TEMPLATE_VARIABLES_SELECTED_BY_NAME,
payload: {
dashboardID,
@ -521,7 +422,7 @@ export const putDashboard = (dashboard: Dashboard) => async (
}
}
export const putDashboardByID = (dashboardID: string) => async (
export const putDashboardByID = (dashboardID: number) => async (
dispatch,
getState
): Promise<void> => {

View File

@ -80,7 +80,7 @@ interface Props {
onCancel: () => void
onSave: (cell: Cell) => void
source: Source
dashboardID: string
dashboardID: number
queryStatus: QueryStatus
autoRefresh: number
templates: Template[]

View File

@ -51,7 +51,7 @@ export const FORMAT_OPTIONS: Array<{text: string}> = [
export type NewDefaultCell = Pick<
Cell,
Exclude<keyof Cell, 'id' | 'axes' | 'colors' | 'links' | 'legend'>
Exclude<keyof Cell, 'i' | 'axes' | 'colors' | 'links' | 'legend'>
>
export const NEW_DEFAULT_DASHBOARD_CELL: NewDefaultCell = {
x: 0,

View File

@ -487,8 +487,6 @@ DashboardPage.propTypes = {
getDashboardsAsync: func.isRequired,
setTimeRange: func.isRequired,
addDashboardCellAsync: func.isRequired,
editDashboardCell: func.isRequired,
cancelEditCell: func.isRequired,
}).isRequired,
dashboards: arrayOf(
shape({

View File

@ -4,7 +4,7 @@ import {NULL_HOVER_TIME} from 'src/shared/constants/tableGraph'
const {lower, upper} = timeRanges.find(tr => tr.lower === 'now() - 1h')
const initialState = {
export const initialState = {
dashboards: [],
timeRange: {lower, upper},
isEditMode: false,
@ -19,7 +19,7 @@ import {
} from 'shared/constants/actionTypes'
import {TEMPLATE_VARIABLE_TYPES} from 'src/dashboards/constants'
export default function ui(state = initialState, action) {
const ui = (state = initialState, action) => {
switch (action.type) {
case 'LOAD_DASHBOARDS': {
const {dashboards} = action.payload
@ -71,23 +71,6 @@ export default function ui(state = initialState, action) {
return {...state, ...newState}
}
case 'UPDATE_DASHBOARD_CELLS': {
const {cells, dashboard} = action.payload
const newDashboard = {
...dashboard,
cells,
}
const newState = {
dashboards: state.dashboards.map(
d => (d.id === dashboard.id ? newDashboard : d)
),
}
return {...state, ...newState}
}
case 'ADD_DASHBOARD_CELL': {
const {cell, dashboard} = action.payload
const {dashboards} = state
@ -102,30 +85,6 @@ export default function ui(state = initialState, action) {
return {...state, ...newState}
}
case 'EDIT_DASHBOARD_CELL': {
const {x, y, isEditing, dashboard} = action.payload
const cell = dashboard.cells.find(c => c.x === x && c.y === y)
const newCell = {
...cell,
isEditing,
}
const newDashboard = {
...dashboard,
cells: dashboard.cells.map(c => (c.x === x && c.y === y ? newCell : c)),
}
const newState = {
dashboards: state.dashboards.map(
d => (d.id === dashboard.id ? newDashboard : d)
),
}
return {...state, ...newState}
}
case 'DELETE_DASHBOARD_CELL': {
const {dashboard, cell} = action.payload
@ -145,24 +104,6 @@ export default function ui(state = initialState, action) {
return {...state, ...newState}
}
case 'CANCEL_EDIT_CELL': {
const {dashboardID, cellID} = action.payload
const dashboards = state.dashboards.map(
d =>
d.id === dashboardID
? {
...d,
cells: d.cells.map(
c => (c.i === cellID ? {...c, isEditing: false} : c)
),
}
: d
)
return {...state, dashboards}
}
case 'SYNC_DASHBOARD_CELL': {
const {cell, dashboard} = action.payload
@ -182,30 +123,6 @@ export default function ui(state = initialState, action) {
return {...state, ...newState}
}
case 'RENAME_DASHBOARD_CELL': {
const {x, y, name, dashboard} = action.payload
const cell = dashboard.cells.find(c => c.x === x && c.y === y)
const newCell = {
...cell,
name,
}
const newDashboard = {
...dashboard,
cells: dashboard.cells.map(c => (c.x === x && c.y === y ? newCell : c)),
}
const newState = {
dashboards: state.dashboards.map(
d => (d.id === dashboard.id ? newDashboard : d)
),
}
return {...state, ...newState}
}
case 'EDIT_CELL_QUERY_STATUS': {
const {queryID, status} = action.payload
@ -338,3 +255,5 @@ export default function ui(state = initialState, action) {
return state
}
export default ui

View File

@ -1,4 +1,3 @@
/* eslint-disable no-magic-numbers */
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'

View File

@ -102,7 +102,7 @@ export default class LayoutCell extends Component<Props> {
if (this.queries.length) {
const child = React.Children.only(children)
return React.cloneElement(child, {cellID: cell.id})
return React.cloneElement(child, {cellID: cell.i})
}
return this.emptyGraph

View File

@ -64,7 +64,7 @@ export interface DecimalPlaces {
}
export interface Cell {
id: string
i: string
x: number
y: number
w: number

View File

@ -1,7 +1,15 @@
import {LayoutCell, LayoutQuery} from './layouts'
import {Service, NewService} from './services'
import {AuthLinks, Organization, Role, User, Me} from './auth'
import {Template, Cell, CellQuery, Legend, Axes, Dashboard} from './dashboard'
import {
Template,
Cell,
CellQuery,
Legend,
Axes,
Dashboard,
CellType,
} from './dashboard'
import {
GroupBy,
Query,
@ -37,6 +45,7 @@ export {
Template,
Cell,
CellQuery,
CellType,
Legend,
Status,
Query,

View File

@ -24,7 +24,7 @@ const setup = (override = {}) => {
cell,
timeRange,
autoRefresh: 0,
dashboardID: '9',
dashboardID: 9,
queryStatus: {
queryID: null,
status: null,

View File

@ -1,43 +1,25 @@
import _ from 'lodash'
import reducer from 'src/dashboards/reducers/ui'
import {template, dashboard, cell} from 'test/resources'
import {initialState} from 'src/dashboards/reducers/ui'
import {
setTimeRange,
loadDashboards,
deleteDashboard,
deleteDashboardFailed,
setTimeRange,
updateDashboardCells,
editDashboardCell,
renameDashboardCell,
syncDashboardCell,
deleteDashboardFailed,
templateVariableSelected,
templateVariablesSelectedByName,
cancelEditCell,
editTemplateVariableValues,
templateVariablesSelectedByName,
setActiveCell,
} from 'src/dashboards/actions'
let state
const t1 = {
id: '1',
type: 'tagKeys',
label: 'test query',
tempVar: ':region:',
query: {
db: 'db1',
rp: 'rp1',
measurement: 'm1',
influxql: 'SHOW TAGS WHERE CHRONOGIRAFFE = "friend"',
},
values: [
{value: 'us-west', type: 'tagKey', selected: false},
{value: 'us-east', type: 'tagKey', selected: true},
{value: 'us-mount', type: 'tagKey', selected: false},
],
}
const t2 = {
...template,
id: '2',
type: 'csv',
label: 'test csv',
@ -49,35 +31,15 @@ const t2 = {
],
}
const templates = [t1, t2]
const templates = [template, t2]
const d1 = {
id: 1,
cells: [],
name: 'd1',
...dashboard,
templates,
}
const d2 = {id: 2, cells: [], name: 'd2', templates: []}
const d2 = {...dashboard, id: 2, cells: [], name: 'd2', templates: []}
const dashboards = [d1, d2]
const c1 = {
x: 0,
y: 0,
w: 4,
h: 4,
id: 1,
i: 'im-a-cell-id-index',
isEditing: false,
name: 'Gigawatts',
}
const editingCell = {
i: 1,
isEditing: true,
name: 'Edit me',
}
const cells = [c1]
describe('DataExplorer.Reducers.UI', () => {
it('can load the dashboards', () => {
@ -90,11 +52,8 @@ describe('DataExplorer.Reducers.UI', () => {
})
it('can delete a dashboard', () => {
const initialState = {...state, dashboards}
const actual = reducer(initialState, deleteDashboard(d1))
const expected = initialState.dashboards.filter(
dashboard => dashboard.id !== d1.id
)
const actual = reducer({...initialState, dashboards}, deleteDashboard(d1))
const expected = dashboards.filter(dash => dash.id !== d1.id)
expect(actual.dashboards).toEqual(expected)
})
@ -117,43 +76,14 @@ describe('DataExplorer.Reducers.UI', () => {
expect(actual.timeRange).toEqual(expected)
})
it('can update dashboard cells', () => {
state = {
dashboards,
}
const updatedCells = [{id: 1}, {id: 2}]
const expected = {
id: 1,
cells: updatedCells,
name: 'd1',
templates,
}
const actual = reducer(state, updateDashboardCells(d1, updatedCells))
expect(actual.dashboards[0]).toEqual(expected)
})
it('can edit a cell', () => {
const dash = {...d1, cells}
state = {
dashboards: [dash],
}
const actual = reducer(state, editDashboardCell(dash, 0, 0, true))
expect(actual.dashboards[0].cells[0].isEditing).toBe(true)
})
it('can sync a cell', () => {
const newCellName = 'watts is kinda cool'
const newCell = {
x: c1.x,
y: c1.y,
...cell,
name: newCellName,
}
const dash = {...d1, cells: [c1]}
const dash = {...d1, cells: [cell]}
state = {
dashboards: [dash],
}
@ -162,22 +92,6 @@ describe('DataExplorer.Reducers.UI', () => {
expect(actual.dashboards[0].cells[0].name).toBe(newCellName)
})
it('can rename cells', () => {
const c2 = {...c1, isEditing: true}
const dash = {...d1, cells: [c2]}
state = {
dashboards: [dash],
}
const actual = reducer(
state,
renameDashboardCell(dash, 0, 0, 'Plutonium Consumption Rate (ug/sec)')
)
expect(actual.dashboards[0].cells[0].name).toBe(
'Plutonium Consumption Rate (ug/sec)'
)
})
it('can select a different template variable', () => {
const dash = _.cloneDeep(d1)
state = {
@ -215,24 +129,20 @@ describe('DataExplorer.Reducers.UI', () => {
expect(actual.dashboards[0].templates[1].values[2].selected).toBe(false)
})
it('can cancel cell editing', () => {
const dash = _.cloneDeep(d1)
dash.cells = [editingCell]
describe('SET_ACTIVE_CELL', () => {
it('can set the active cell', () => {
const activeCellID = '1'
const actual = reducer(initialState, setActiveCell(activeCellID))
const actual = reducer(
{dashboards: [dash]},
cancelEditCell(dash.id, editingCell.i)
)
expect(actual.dashboards[0].cells[0].isEditing).toBe(false)
expect(actual.dashboards[0].cells[0].name).toBe(editingCell.name)
expect(actual.activeCellID).toEqual(activeCellID)
})
})
describe('EDIT_TEMPLATE_VARIABLE_VALUES', () => {
it('can edit the tempvar values', () => {
const actual = reducer(
{dashboards},
editTemplateVariableValues(d1.id, t1.id, ['v1', 'v2'])
{...initialState, dashboards},
editTemplateVariableValues(d1.id, template.id, ['v1', 'v2'])
)
const expected = [
@ -252,12 +162,12 @@ describe('DataExplorer.Reducers.UI', () => {
})
it('can handle an empty template.values', () => {
const ts = [{...t1, values: []}]
const ts = [{...template, values: []}]
const ds = [{...d1, templates: ts}]
const actual = reducer(
{dashboards: ds},
editTemplateVariableValues(d1.id, t1.id, ['v1', 'v2'])
{...initialState, dashboards: ds},
editTemplateVariableValues(d1.id, template.id, ['v1', 'v2'])
)
const expected = [

View File

@ -161,7 +161,7 @@ export const decimalPlaces: DecimalPlaces = {
}
export const cell: Cell = {
id: '67435af2-17bf-4caa-a5fc-0dd1ffb40dab',
i: '67435af2-17bf-4caa-a5fc-0dd1ffb40dab',
x: 0,
y: 0,
w: 8,

View File

@ -1,4 +1,4 @@
import {Source} from 'src/types'
import {Source, Template, Dashboard, Cell, CellType} from 'src/types'
import {SourceLinks} from 'src/types/sources'
export const role = {
@ -586,3 +586,83 @@ export const hosts = {
load: 0,
},
}
// Dashboards
export const template: Template = {
id: '1',
type: 'tagKeys',
label: 'test query',
tempVar: ':region:',
query: {
db: 'db1',
command: '',
rp: 'rp1',
tagKey: 'tk1',
fieldKey: 'fk1',
measurement: 'm1',
influxql: 'SHOW TAGS WHERE CHRONOGIRAFFE = "friend"',
},
values: [
{value: 'us-west', type: 'tagKey', selected: false},
{value: 'us-east', type: 'tagKey', selected: true},
{value: 'us-mount', type: 'tagKey', selected: false},
],
}
export const dashboard: Dashboard = {
id: 1,
cells: [],
name: 'd1',
templates: [],
organization: 'thebestorg',
}
export const cell: Cell = {
x: 0,
y: 0,
w: 4,
h: 4,
i: '0246e457-916b-43e3-be99-211c4cbc03e8',
name: 'Apache Bytes/Second',
queries: [],
axes: {
x: {
bounds: ['', ''],
label: '',
prefix: '',
suffix: '',
base: '',
scale: '',
},
y: {
bounds: ['', ''],
label: '',
prefix: '',
suffix: '',
base: '',
scale: '',
},
},
type: CellType.Line,
colors: [],
tableOptions: {
verticalTimeAxis: true,
sortBy: {
internalName: '',
displayName: '',
visible: true,
},
fixFirstColumn: true,
},
fieldOptions: [],
timeFormat: '',
decimalPlaces: {
isEnforced: false,
digits: 1,
},
links: {
self:
'/chronograf/v1/dashboards/10/cells/8b3b7897-49b1-422c-9443-e9b778bcbf12',
},
legend: {},
}