Merge branch 'master' into bugfix/kapacitor-loading-4ever

pull/10616/head
Deniz Kusefoglu 2018-04-03 16:46:08 -04:00 committed by GitHub
commit 1724b532c9
51 changed files with 1024 additions and 422 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.4.3.0
current_version = 1.4.3.1
files = README.md server/swagger.json
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.(?P<release>\d+)
serialize = {major}.{minor}.{patch}.{release}

View File

@ -1,13 +1,10 @@
Closes #
_Briefly describe your proposed changes:_
_What was the problem?_
_What was the solution?_
- [ ] CHANGELOG.md updated with a link to the PR (not the Issue)
- [ ] Rebased/mergable
- [ ] Tests pass
- [ ] Sign [CLA](https://influxdata.com/community/cla/) (if not already signed)
Connect #
### The problem
### The Solution
- [ ] Sign [CLA](https://influxdata.com/community/cla/) (if not already signed)

View File

@ -3,7 +3,9 @@
### Features
1. [#2526](https://github.com/influxdata/chronograf/pull/2526): Add support for RS256/JWKS verification, support for id_token parsing (as in ADFS)
1. [#3060](https://github.com/influxdata/chronograf/pull/3060): Add ability to set a color palette for Line, Stacked, Step-Plot, and Bar graphs
1. [#3103](https://github.com/influxdata/chronograf/pull/3103): Add ability to clone dashboards
1. [#3080](https://github.com/influxdata/chronograf/pull/3080): Add tabular data visualization option with features
### UI Improvements
@ -14,8 +16,15 @@
### Bug Fixes
1. [#2950](https://github.com/influxdata/chronograf/pull/2094): Always save template variables on first edit
1. [#3101](https://github.com/influxdata/chronograf/pull/3101): Fix template variables not loading
1. [#3109](https://github.com/influxdata/chronograf/pull/3109): Display link to configure Kapacitor on Alerts Page if no configured kapacitor.
1. [#3111](https://github.com/influxdata/chronograf/pull/3111): Fix saving of new TICKscripts
## v1.4.3.1 [2018-04-02]
### Bug Fixes
1. [#3107](https://github.com/influxdata/chronograf/pull/3107): Fixes template variable editing not allowing saving
1. [#3094](https://github.com/influxdata/chronograf/pull/3094): Save template variables on first edit
1. [#3101](https://github.com/influxdata/chronograf/pull/3101): Fix template variables not loading all values
## v1.4.3.0 [2018-3-28]

View File

@ -136,7 +136,7 @@ option.
## Versions
The most recent version of Chronograf is
[v1.4.3.0](https://www.influxdata.com/downloads/).
[v1.4.3.1](https://www.influxdata.com/downloads/).
Spotted a bug or have a feature request? Please open
[an issue](https://github.com/influxdata/chronograf/issues/new)!
@ -178,7 +178,7 @@ By default, chronograf runs on port `8888`.
To get started right away with Docker, you can pull down our latest release:
```sh
docker pull chronograf:1.4.3.0
docker pull chronograf:1.4.3.1
```
### From Source

View File

@ -113,7 +113,7 @@ func HasCorrectAxes(c *chronograf.DashboardCell) error {
// HasCorrectColors verifies that the format of each color is correct
func HasCorrectColors(c *chronograf.DashboardCell) error {
for _, color := range c.CellColors {
if !oneOf(color.Type, "max", "min", "threshold", "text", "background") {
if !oneOf(color.Type, "max", "min", "threshold", "text", "background", "scale") {
return chronograf.ErrInvalidColorType
}
if len(color.Hex) != 7 {

View File

@ -635,7 +635,7 @@ type sourceUsersResponse struct {
}
func (r *sourceUserRequest) ValidUpdate() error {
if r.Password == "" && len(r.Permissions) == 0 && len(r.Roles) == 0 {
if r.Password == "" && r.Permissions == nil && r.Roles == nil {
return fmt.Errorf("No fields to update")
}
return validPermissions(&r.Permissions)

View File

@ -3,7 +3,7 @@
"info": {
"title": "Chronograf",
"description": "API endpoints for Chronograf",
"version": "1.4.3.0"
"version": "1.4.3.1"
},
"schemes": ["http"],
"basePath": "/chronograf/v1",

View File

@ -1,6 +1,6 @@
{
"name": "chronograf-ui",
"version": "1.4.3-0",
"version": "1.4.3-1",
"private": false,
"license": "AGPL-3.0",
"description": "",
@ -116,6 +116,7 @@
"axios": "^0.13.1",
"bignumber.js": "^4.0.2",
"calculate-size": "^1.1.1",
"chroma-js": "^1.3.6",
"classnames": "^2.2.3",
"dygraphs": "2.1.0",
"eslint-plugin-babel": "^4.1.2",
@ -147,4 +148,4 @@
"rome": "^2.1.22",
"uuid": "^3.2.1"
}
}
}

View File

@ -0,0 +1,66 @@
import React, {PureComponent} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
import MultiSelectDropdown from 'src/shared/components/MultiSelectDropdown'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
import {User} from 'src/types/influxAdmin'
interface Props {
user: User
allPermissions: string[]
onUpdatePermissions: (user: User, permissions: any[]) => void
}
class UserPermissionsDropdown extends PureComponent<Props> {
public render() {
return (
<MultiSelectDropdown
buttonSize="btn-xs"
buttonColor="btn-primary"
resetStateOnReceiveProps={false}
items={this.allPermissions}
label={this.permissionsLabel}
customClass={this.permissionsClass}
selectedItems={this.selectedPermissions}
onApply={this.handleUpdatePermissions}
/>
)
}
private handleUpdatePermissions = (permissions): void => {
const {onUpdatePermissions, user} = this.props
const allowed = permissions.map(p => p.name)
onUpdatePermissions(user, [{scope: 'all', allowed}])
}
private get allPermissions() {
return this.props.allPermissions.map(p => ({name: p}))
}
private get userPermissions() {
return _.get(this.props.user, ['permissions', '0', 'allowed'], [])
}
private get selectedPermissions() {
return this.userPermissions.map(p => ({name: p}))
}
private get permissionsLabel() {
const {user} = this.props
if (user.permissions && user.permissions.length) {
return 'Select Permissions'
}
return ''
}
private get permissionsClass() {
return classnames(`dropdown-${USERS_TABLE.colPermissions}`, {
'admin-table--multi-select-empty': !this.props.user.permissions.length,
})
}
}
export default UserPermissionsDropdown

View File

@ -0,0 +1,58 @@
import React, {PureComponent} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
import MultiSelectDropdown from 'src/shared/components/MultiSelectDropdown'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
import {User, UserRole} from 'src/types/influxAdmin'
interface Props {
user: User
allRoles: any[]
onUpdateRoles: (user: User, roles: UserRole[]) => void
}
class UserRoleDropdown extends PureComponent<Props> {
public render() {
const {allRoles} = this.props
return (
<MultiSelectDropdown
buttonSize="btn-xs"
buttonColor="btn-primary"
items={allRoles}
label={this.rolesLabel}
selectedItems={this.roles}
customClass={this.rolesClass}
onApply={this.handleUpdateRoles}
resetStateOnReceiveProps={false}
/>
)
}
private handleUpdateRoles = (roleNames): void => {
const {user, allRoles, onUpdateRoles} = this.props
const roles = allRoles.filter(r => roleNames.find(rn => rn.name === r.name))
onUpdateRoles(user, roles)
}
private get roles(): UserRole[] {
const roles = _.get(this.props.user, 'roles', []) as UserRole[]
return roles.map(({name}) => ({name}))
}
private get rolesClass(): string {
return classnames(`dropdown-${USERS_TABLE.colRoles}`, {
'admin-table--multi-select-empty': !this.roles.length,
})
}
private get rolesLabel(): string {
return this.roles.length ? '' : 'Select Roles'
}
}
export default UserRoleDropdown

View File

@ -1,169 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import classnames from 'classnames'
import UserEditName from 'src/admin/components/UserEditName'
import UserNewPassword from 'src/admin/components/UserNewPassword'
import MultiSelectDropdown from 'shared/components/MultiSelectDropdown'
import ConfirmOrCancel from 'shared/components/ConfirmOrCancel'
import ConfirmButton from 'shared/components/ConfirmButton'
import ChangePassRow from 'src/admin/components/ChangePassRow'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
const UserRow = ({
user: {name, roles = [], permissions, password},
user,
allRoles,
allPermissions,
hasRoles,
isNew,
isEditing,
onEdit,
onSave,
onCancel,
onDelete,
onUpdatePermissions,
onUpdateRoles,
onUpdatePassword,
}) => {
function handleUpdatePermissions(perms) {
const allowed = perms.map(p => p.name)
onUpdatePermissions(user, [{scope: 'all', allowed}])
}
function handleUpdateRoles(roleNames) {
onUpdateRoles(
user,
allRoles.filter(r => roleNames.find(rn => rn.name === r.name))
)
}
function handleUpdatePassword() {
onUpdatePassword(user, password)
}
const perms = _.get(permissions, ['0', 'allowed'], [])
const wrappedDelete = () => {
onDelete(user)
}
if (isEditing) {
return (
<tr className="admin-table--edit-row">
<UserEditName user={user} onEdit={onEdit} onSave={onSave} />
<UserNewPassword
user={user}
onEdit={onEdit}
onSave={onSave}
isNew={isNew}
/>
{hasRoles ? <td className="admin-table--left-offset">--</td> : null}
<td className="admin-table--left-offset">--</td>
<td
className="text-right"
style={{width: `${USERS_TABLE.colDelete}px`}}
>
<ConfirmOrCancel
item={user}
onConfirm={onSave}
onCancel={onCancel}
buttonSize="btn-xs"
/>
</td>
</tr>
)
}
return (
<tr>
<td style={{width: `${USERS_TABLE.colUsername}px`}}>{name}</td>
<td style={{width: `${USERS_TABLE.colPassword}px`}}>
<ChangePassRow
onEdit={onEdit}
onApply={handleUpdatePassword}
user={user}
buttonSize="btn-xs"
/>
</td>
{hasRoles ? (
<td>
<MultiSelectDropdown
items={allRoles}
selectedItems={roles.map(r => ({name: r.name}))}
label={roles.length ? '' : 'Select Roles'}
onApply={handleUpdateRoles}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(`dropdown-${USERS_TABLE.colRoles}`, {
'admin-table--multi-select-empty': !roles.length,
})}
resetStateOnReceiveProps={false}
/>
</td>
) : null}
<td>
{allPermissions && allPermissions.length ? (
<MultiSelectDropdown
items={allPermissions.map(p => ({name: p}))}
selectedItems={perms.map(p => ({name: p}))}
label={
permissions && permissions.length ? '' : 'Select Permissions'
}
onApply={handleUpdatePermissions}
buttonSize="btn-xs"
buttonColor="btn-primary"
customClass={classnames(`dropdown-${USERS_TABLE.colPermissions}`, {
'admin-table--multi-select-empty': !permissions.length,
})}
resetStateOnReceiveProps={false}
/>
) : null}
</td>
<td className="text-right" style={{width: `${USERS_TABLE.colDelete}px`}}>
<ConfirmButton
size="btn-xs"
type="btn-danger"
text="Delete User"
confirmAction={wrappedDelete}
customClass="table--show-on-row-hover"
/>
</td>
</tr>
)
}
const {arrayOf, bool, func, shape, string} = PropTypes
UserRow.propTypes = {
user: shape({
name: string,
roles: arrayOf(
shape({
name: string,
})
),
permissions: arrayOf(
shape({
name: string,
})
),
password: string,
}).isRequired,
allRoles: arrayOf(shape()),
allPermissions: arrayOf(string),
hasRoles: bool,
isNew: bool,
isEditing: bool,
onCancel: func,
onEdit: func,
onSave: func,
onDelete: func.isRequired,
onUpdatePermissions: func,
onUpdateRoles: func,
onUpdatePassword: func,
}
export default UserRow

View File

@ -0,0 +1,120 @@
import React, {PureComponent} from 'react'
import UserPermissionsDropdown from 'src/admin/components/UserPermissionsDropdown'
import UserRoleDropdown from 'src/admin/components/UserRoleDropdown'
import ChangePassRow from 'src/admin/components/ChangePassRow'
import ConfirmButton from 'src/shared/components/ConfirmButton'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
import UserRowEdit from 'src/admin/components/UserRowEdit'
import {User} from 'src/types/influxAdmin'
interface UserRowProps {
user: User
allRoles: any[]
allPermissions: string[]
hasRoles: boolean
isNew: boolean
isEditing: boolean
onCancel: () => void
onEdit: () => void
onSave: () => void
onDelete: (user: User) => void
onUpdatePermissions: (user: User, permissions: any[]) => void
onUpdateRoles: (user: User, roles: any[]) => void
onUpdatePassword: (user: User, password: string) => void
}
class UserRow extends PureComponent<UserRowProps> {
public render() {
const {
user,
allRoles,
allPermissions,
hasRoles,
isNew,
isEditing,
onEdit,
onSave,
onCancel,
onUpdatePermissions,
onUpdateRoles,
} = this.props
if (isEditing) {
return (
<UserRowEdit
user={user}
isNew={isNew}
onEdit={onEdit}
onSave={onSave}
onCancel={onCancel}
hasRoles={hasRoles}
/>
)
}
return (
<tr>
<td style={{width: `${USERS_TABLE.colUsername}px`}}>{user.name}</td>
<td style={{width: `${USERS_TABLE.colPassword}px`}}>
<ChangePassRow
user={user}
onEdit={onEdit}
buttonSize="btn-xs"
onApply={this.handleUpdatePassword}
/>
</td>
{hasRoles && (
<td>
<UserRoleDropdown
user={user}
allRoles={allRoles}
onUpdateRoles={onUpdateRoles}
/>
</td>
)}
<td>
{this.hasPermissions && (
<UserPermissionsDropdown
user={user}
allPermissions={allPermissions}
onUpdatePermissions={onUpdatePermissions}
/>
)}
</td>
<td
className="text-right"
style={{width: `${USERS_TABLE.colDelete}px`}}
>
<ConfirmButton
size="btn-xs"
type="btn-danger"
text="Delete User"
confirmAction={this.handleDelete}
customClass="table--show-on-row-hover"
/>
</td>
</tr>
)
}
private handleDelete = (): void => {
const {user, onDelete} = this.props
onDelete(user)
}
private handleUpdatePassword = (): void => {
const {user, onUpdatePassword} = this.props
onUpdatePassword(user, user.password)
}
private get hasPermissions() {
const {allPermissions} = this.props
return allPermissions && !!allPermissions.length
}
}
export default UserRow

View File

@ -0,0 +1,47 @@
import React, {SFC} from 'react'
import UserEditName from 'src/admin/components/UserEditName'
import UserNewPassword from 'src/admin/components/UserNewPassword'
import ConfirmOrCancel from 'src/shared/components/ConfirmOrCancel'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
import {User} from 'src/types/influxAdmin'
interface UserRowEditProps {
user: User
onEdit: () => void
onSave: () => void
onCancel: () => void
isNew: boolean
hasRoles: boolean
}
const UserRowEdit: SFC<UserRowEditProps> = ({
user,
onEdit,
onSave,
onCancel,
isNew,
hasRoles,
}) => (
<tr className="admin-table--edit-row">
<UserEditName user={user} onEdit={onEdit} onSave={onSave} />
<UserNewPassword
user={user}
onEdit={onEdit}
onSave={onSave}
isNew={isNew}
/>
{hasRoles ? <td className="admin-table--left-offset">--</td> : null}
<td className="admin-table--left-offset">--</td>
<td className="text-right" style={{width: `${USERS_TABLE.colDelete}px`}}>
<ConfirmOrCancel
item={user}
onConfirm={onSave}
onCancel={onCancel}
buttonSize="btn-xs"
/>
</td>
</tr>
)
export default UserRowEdit

View File

@ -57,3 +57,10 @@ export const updateTableOptions = tableOptions => ({
tableOptions,
},
})
export const updateLineColors = lineColors => ({
type: 'UPDATE_LINE_COLORS',
payload: {
lineColors,
},
})

View File

@ -7,6 +7,7 @@ import OptIn from 'shared/components/OptIn'
import Input from 'src/dashboards/components/DisplayOptionsInput'
import {Tabber, Tab} from 'src/dashboards/components/Tabber'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import LineGraphColorSelector from 'src/shared/components/LineGraphColorSelector'
import {
AXES_SCALE_OPTIONS,
@ -102,6 +103,7 @@ class AxesOptions extends Component {
type="text"
/>
</div>
<LineGraphColorSelector />
<div className="form-group col-sm-6">
<label htmlFor="min">Min</label>
<OptIn

View File

@ -24,7 +24,8 @@ import {
import {OVERLAY_TECHNOLOGY} from 'src/shared/constants/classNames'
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
import {AUTO_GROUP_BY} from 'src/shared/constants'
import {stringifyColorValues} from 'src/shared/constants/colorOperations'
import {getCellTypeColors} from 'src/dashboards/constants/cellEditor'
import {colorsStringSchema, colorsNumberSchema} from 'shared/schemas'
class CellEditorOverlay extends Component {
constructor(props) {
@ -107,7 +108,7 @@ class CellEditorOverlay extends Component {
handleSaveCell = () => {
const {queriesWorkingDraft, staticLegend} = this.state
const {cell, thresholdsListColors, gaugeColors} = this.props
const {cell, thresholdsListColors, gaugeColors, lineColors} = this.props
const queries = queriesWorkingDraft.map(q => {
const timeRange = q.range || {upper: null, lower: ':dashboardTime:'}
@ -120,20 +121,12 @@ class CellEditorOverlay extends Component {
}
})
let colors = []
switch (cell.type) {
case 'gauge': {
colors = stringifyColorValues(gaugeColors)
break
}
case 'single-stat':
case 'line-plus-single-stat':
case 'table': {
colors = stringifyColorValues(thresholdsListColors)
break
}
}
const colors = getCellTypeColors({
cellType: cell.type,
gaugeColors,
thresholdsListColors,
lineColors,
})
this.props.onSave({
...cell,
@ -390,8 +383,9 @@ CellEditorOverlay.propTypes = {
dashboardID: string.isRequired,
sources: arrayOf(shape()),
thresholdsListType: string.isRequired,
thresholdsListColors: arrayOf(shape({}).isRequired).isRequired,
gaugeColors: arrayOf(shape({}).isRequired).isRequired,
thresholdsListColors: colorsNumberSchema.isRequired,
gaugeColors: colorsNumberSchema.isRequired,
lineColors: colorsStringSchema.isRequired,
}
CEOBottom.propTypes = {

View File

@ -20,6 +20,7 @@ import {
updateGaugeColors,
updateAxes,
} from 'src/dashboards/actions/cellEditorOverlay'
import {colorsNumberSchema} from 'shared/schemas'
class GaugeOptions extends Component {
handleAddThreshold = () => {
@ -219,18 +220,10 @@ class GaugeOptions extends Component {
}
}
const {arrayOf, func, number, shape, string} = PropTypes
const {func, shape} = PropTypes
GaugeOptions.propTypes = {
gaugeColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: number.isRequired,
}).isRequired
),
gaugeColors: colorsNumberSchema,
handleUpdateGaugeColors: func.isRequired,
handleUpdateAxes: func.isRequired,
axes: shape({}).isRequired,

View File

@ -6,7 +6,8 @@ import RefreshingGraph from 'src/shared/components/RefreshingGraph'
import buildQueries from 'utils/buildQueriesForGraphs'
import VisualizationName from 'src/dashboards/components/VisualizationName'
import {stringifyColorValues} from 'src/shared/constants/colorOperations'
import {getCellTypeColors} from 'src/dashboards/constants/cellEditor'
import {colorsStringSchema, colorsNumberSchema} from 'shared/schemas'
const DashVisualization = (
{
@ -14,6 +15,7 @@ const DashVisualization = (
type,
templates,
timeRange,
lineColors,
autoRefresh,
gaugeColors,
queryConfigs,
@ -26,14 +28,19 @@ const DashVisualization = (
},
{source: {links: {proxy}}}
) => {
const colors = type === 'gauge' ? gaugeColors : thresholdsListColors
const colors = getCellTypeColors({
cellType: type,
gaugeColors,
thresholdsListColors,
lineColors,
})
return (
<div className="graph">
<VisualizationName />
<div className="graph-container">
<RefreshingGraph
colors={stringifyColorValues(colors)}
colors={colors}
axes={axes}
type={type}
tableOptions={tableOptions}
@ -69,24 +76,9 @@ DashVisualization.propTypes = {
}),
tableOptions: shape({}),
resizerTopHeight: number,
thresholdsListColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: number.isRequired,
}).isRequired
),
gaugeColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: number.isRequired,
}).isRequired
),
thresholdsListColors: colorsNumberSchema,
gaugeColors: colorsNumberSchema,
lineColors: colorsStringSchema,
staticLegend: bool,
setDataLabels: func,
}
@ -103,11 +95,13 @@ const mapStateToProps = ({
cellEditorOverlay: {
thresholdsListColors,
gaugeColors,
lineColors,
cell: {type, axes, tableOptions},
},
}) => ({
gaugeColors,
thresholdsListColors,
lineColors,
type,
axes,
tableOptions,

View File

@ -70,7 +70,7 @@ const TemplateVariableRow = ({
<Dropdown
items={TEMPLATE_TYPES}
onChoose={onSelectType}
onClick={onStartEdit}
onClick={onStartEdit('tempVar')}
selected={TEMPLATE_TYPES.find(t => t.type === selectedType).text}
className="dropdown-140"
/>
@ -84,7 +84,7 @@ const TemplateVariableRow = ({
selectedMeasurement={selectedMeasurement}
selectedTagKey={selectedTagKey}
onSelectTagKey={onSelectTagKey}
onStartEdit={onStartEdit}
onStartEdit={onStartEdit('tempVar')}
onErrorThrown={onErrorThrown}
/>
<RowValues

View File

@ -1,4 +1,5 @@
import {DEFAULT_TABLE_OPTIONS} from 'src/shared/constants/tableGraph'
import {stringifyColorValues} from 'src/shared/constants/colorOperations'
export const initializeOptions = cellType => {
switch (cellType) {
@ -18,3 +19,33 @@ export const AXES_SCALE_OPTIONS = {
export const TOOLTIP_Y_VALUE_FORMAT =
'<p><strong>K/M/B</strong> = Thousand / Million / Billion<br/><strong>K/M/G</strong> = Kilo / Mega / Giga </p>'
export const getCellTypeColors = ({
cellType,
gaugeColors,
thresholdsListColors,
lineColors,
}) => {
let colors = []
switch (cellType) {
case 'gauge': {
colors = stringifyColorValues(gaugeColors)
break
}
case 'single-stat':
case 'line-plus-single-stat':
case 'table': {
colors = stringifyColorValues(thresholdsListColors)
break
}
case 'bar':
case 'line':
case 'line-stacked':
case 'line-stepplot': {
colors = stringifyColorValues(lineColors)
}
}
return colors
}

View File

@ -37,6 +37,7 @@ import {
import {presentationButtonDispatcher} from 'shared/dispatchers'
import {DASHBOARD_LAYOUT_ROW_HEIGHT} from 'shared/constants'
import {notifyDashboardNotFound} from 'shared/copy/notifications'
import {colorsStringSchema, colorsNumberSchema} from 'shared/schemas'
const FORMAT_INFLUXQL = 'influxql'
const defaultTimeRange = {
@ -283,6 +284,7 @@ class DashboardPage extends Component {
showTemplateControlBar,
dashboard,
dashboards,
lineColors,
gaugeColors,
autoRefresh,
selectedCell,
@ -399,6 +401,7 @@ class DashboardPage extends Component {
thresholdsListType={thresholdsListType}
thresholdsListColors={thresholdsListColors}
gaugeColors={gaugeColors}
lineColors={lineColors}
/>
) : null}
<DashboardHeader
@ -530,8 +533,9 @@ DashboardPage.propTypes = {
handleDismissEditingAnnotation: func.isRequired,
selectedCell: shape({}),
thresholdsListType: string.isRequired,
thresholdsListColors: arrayOf(shape({}).isRequired).isRequired,
gaugeColors: arrayOf(shape({}).isRequired).isRequired,
thresholdsListColors: colorsNumberSchema.isRequired,
gaugeColors: colorsNumberSchema.isRequired,
lineColors: colorsStringSchema.isRequired,
}
const mapStateToProps = (state, {params: {dashboardID}}) => {
@ -549,6 +553,7 @@ const mapStateToProps = (state, {params: {dashboardID}}) => {
thresholdsListType,
thresholdsListColors,
gaugeColors,
lineColors,
},
} = state
@ -579,6 +584,7 @@ const mapStateToProps = (state, {params: {dashboardID}}) => {
thresholdsListType,
thresholdsListColors,
gaugeColors,
lineColors,
}
}

View File

@ -545,10 +545,9 @@ export const GRAPH_TYPES = [
menuOption: 'Gauge',
graphic: GRAPH_SVGS.gauge,
},
// FEATURE FLAG for Table-Graph
// {
// type: 'table',
// menuOption: 'Table',
// graphic: GRAPH_SVGS.table,
// },
{
type: 'table',
menuOption: 'Table',
graphic: GRAPH_SVGS.table,
},
]

View File

@ -9,6 +9,11 @@ import {
getThresholdsListType,
} from 'shared/constants/thresholds'
import {
DEFAULT_LINE_COLORS,
validateLineColors,
} from 'src/shared/constants/graphColorPalettes'
import {initializeOptions} from 'src/dashboards/constants/cellEditor'
export const initialState = {
@ -16,6 +21,7 @@ export const initialState = {
thresholdsListType: THRESHOLD_TYPE_TEXT,
thresholdsListColors: DEFAULT_THRESHOLDS_LIST_COLORS,
gaugeColors: DEFAULT_GAUGE_COLORS,
lineColors: DEFAULT_LINE_COLORS,
}
export default function cellEditorOverlay(state = initialState, action) {
@ -36,12 +42,15 @@ export default function cellEditorOverlay(state = initialState, action) {
initializeOptions('table')
)
const lineColors = validateLineColors(colors)
return {
...state,
cell: {...cell, tableOptions},
thresholdsListType,
thresholdsListColors,
gaugeColors,
lineColors,
}
}
@ -101,6 +110,12 @@ export default function cellEditorOverlay(state = initialState, action) {
return {...state, cell}
}
case 'UPDATE_LINE_COLORS': {
const {lineColors} = action.payload
return {...state, lineColors}
}
}
return state

View File

@ -55,8 +55,8 @@ const generateTemplateVariableQuery = ({
export const makeQueryForTemplate = ({influxql, db, measurement, tagKey}) =>
influxql
.replace(':database:', db)
.replace(':measurement:', measurement)
.replace(':tagKey:', tagKey)
.replace(':database:', `"${db}"`)
.replace(':measurement:', `"${measurement}"`)
.replace(':tagKey:', `"${tagKey}"`)
export default generateTemplateVariableQuery

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types'
import Table from './Table'
import RefreshingGraph from 'shared/components/RefreshingGraph'
import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes'
const VisView = ({
axes,
@ -37,6 +38,7 @@ const VisView = ({
return (
<RefreshingGraph
colors={DEFAULT_LINE_COLORS}
axes={axes}
type={cellType}
queries={queries}

View File

@ -8,6 +8,8 @@ import underlayCallback from 'src/kapacitor/helpers/ruleGraphUnderlay'
const RefreshingLineGraph = AutoRefresh(LineGraph)
import {LINE_COLORS_RULE_GRAPH} from 'src/shared/constants/graphColorPalettes'
const {shape, string, func} = PropTypes
const RuleGraph = ({
query,
@ -20,7 +22,6 @@ const RuleGraph = ({
const autoRefreshMs = 30000
const queryText = buildInfluxQLQuery({lower}, query)
const queries = [{host: source.links.proxy, text: queryText}]
const kapacitorLineColors = ['#4ED8A0']
if (!queryText) {
return (
@ -47,7 +48,7 @@ const RuleGraph = ({
isGraphFilled={false}
ruleValues={rule.values}
autoRefresh={autoRefreshMs}
overrideLineColors={kapacitorLineColors}
colors={LINE_COLORS_RULE_GRAPH}
underlayCallback={underlayCallback(rule)}
/>
</div>

View File

@ -19,7 +19,7 @@ const TickscriptEditorControls = ({
{isNewTickscript ? (
<TickscriptID onChangeID={onChangeID} id={task.id} />
) : (
<TickscriptStaticID id={task.name} />
<TickscriptStaticID id={task.id} />
)}
<div className="tickscript-controls--right">
<TickscriptType type={task.type} onChangeType={onChangeType} />

View File

@ -173,6 +173,7 @@ class TickscriptPage extends Component {
response = await updateTask(kapacitor, task, ruleID, router, sourceID)
} else {
response = await createTask(kapacitor, task, router, sourceID)
router.push(`/sources/${sourceID}/tickscript/${response.id}`)
}
if (response.code) {
this.setState({unsavedChanges: true, consoleMessage: response.message})
@ -227,6 +228,7 @@ class TickscriptPage extends Component {
unsavedChanges,
consoleMessage,
} = this.state
return (
<Tickscript
task={task}

View File

@ -99,7 +99,7 @@ ColorDropdown.propTypes = {
shape({
hex: string.isRequired,
name: string.isRequired,
})
}).isRequired
).isRequired,
stretchToFit: bool,
disabled: bool,

View File

@ -0,0 +1,117 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import uuid from 'uuid'
import classnames from 'classnames'
import OnClickOutside from 'shared/components/OnClickOutside'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import {LINE_COLOR_SCALES} from 'src/shared/constants/graphColorPalettes'
class ColorScaleDropdown extends Component {
constructor(props) {
super(props)
this.state = {
expanded: false,
}
}
handleToggleMenu = () => {
const {disabled} = this.props
if (disabled) {
return
}
this.setState({expanded: !this.state.expanded})
}
handleClickOutside = () => {
this.setState({expanded: false})
}
handleDropdownClick = colorScale => () => {
this.props.onChoose(colorScale)
this.setState({expanded: false})
}
generateGradientStyle = colors => ({
background: `linear-gradient(to right, ${colors[0].hex} 0%,${
colors[1].hex
} 50%,${colors[2].hex} 100%)`,
})
render() {
const {expanded} = this.state
const {selected, disabled, stretchToFit} = this.props
const dropdownClassNames = classnames('color-dropdown', {
open: expanded,
'color-dropdown--stretch': stretchToFit,
})
const toggleClassNames = classnames(
'btn btn-sm btn-default color-dropdown--toggle',
{active: expanded, 'color-dropdown__disabled': disabled}
)
return (
<div className={dropdownClassNames}>
<div
className={toggleClassNames}
onClick={this.handleToggleMenu}
disabled={disabled}
>
<div
className="color-dropdown--swatches"
style={this.generateGradientStyle(selected)}
/>
<div className="color-dropdown--name">{selected[0].name}</div>
<span className="caret" />
</div>
{expanded ? (
<div className="color-dropdown--menu">
<FancyScrollbar autoHide={false} autoHeight={true}>
{LINE_COLOR_SCALES.map(colorScale => (
<div
className={
colorScale.name === selected[0].name
? 'color-dropdown--item active'
: 'color-dropdown--item'
}
key={uuid.v4()}
onClick={this.handleDropdownClick(colorScale)}
>
<div
className="color-dropdown--swatches"
style={this.generateGradientStyle(colorScale.colors)}
/>
<span className="color-dropdown--name">
{colorScale.name}
</span>
</div>
))}
</FancyScrollbar>
</div>
) : null}
</div>
)
}
}
const {arrayOf, bool, func, shape, string} = PropTypes
ColorScaleDropdown.propTypes = {
selected: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
}).isRequired
).isRequired,
onChoose: func.isRequired,
stretchToFit: bool,
disabled: bool,
}
export default OnClickOutside(ColorScaleDropdown)

View File

@ -23,11 +23,17 @@ import {
LABEL_WIDTH,
CHAR_PIXELS,
barPlotter,
hasherino,
highlightSeriesOpts,
} from 'src/shared/graphs/helpers'
import {
DEFAULT_LINE_COLORS,
getLineColorsHexes,
} from 'src/shared/constants/graphColorPalettes'
const {LINEAR, LOG, BASE_10, BASE_2} = AXES_SCALE_OPTIONS
import {colorsStringSchema} from 'shared/schemas'
class Dygraph extends Component {
constructor(props) {
super(props)
@ -53,7 +59,7 @@ class Dygraph extends Component {
fillGraph,
logscale: y.scale === LOG,
colors: this.getLineColors(),
series: this.hashColorDygraphSeries(),
series: this.colorDygraphSeries(),
plugins: [new Dygraphs.Plugins.Crosshair({direction: 'vertical'})],
axes: {
y: {
@ -149,7 +155,7 @@ class Dygraph extends Component {
},
},
colors: this.getLineColors(),
series: this.hashColorDygraphSeries(),
series: this.colorDygraphSeries(),
plotter: isBarGraph ? barPlotter : null,
drawCallback: this.annotationsRef.heartbeat,
}
@ -189,6 +195,33 @@ class Dygraph extends Component {
onZoom(this.formatTimeRange(lower), this.formatTimeRange(upper))
}
colorDygraphSeries = () => {
const {dygraphSeries, children, colors, overrideLineColors} = this.props
const numSeries = Object.keys(dygraphSeries).length
let lineColors = getLineColorsHexes(colors, numSeries)
if (React.children && React.children.count(children)) {
// If graph is line-plus-single-stat then reserve colors for single stat
lineColors = getLineColorsHexes(DEFAULT_LINE_COLORS, numSeries)
}
if (overrideLineColors) {
lineColors = getLineColorsHexes(overrideLineColors, numSeries)
}
const coloredDygraphSeries = {}
for (const seriesName in dygraphSeries) {
const series = dygraphSeries[seriesName]
const color = lineColors[Object.keys(dygraphSeries).indexOf(seriesName)]
coloredDygraphSeries[seriesName] = {...series, color}
}
return coloredDygraphSeries
}
eventToTimestamp = ({pageX: pxBetweenMouseAndPage}) => {
const {left: pxBetweenGraphAndPage} = this.graphRef.getBoundingClientRect()
const graphXCoordinate = pxBetweenMouseAndPage - pxBetweenGraphAndPage
@ -213,21 +246,6 @@ class Dygraph extends Component {
this.setState({isHoveringThisGraph: false})
}
hashColorDygraphSeries = () => {
const {dygraphSeries} = this.props
const colors = this.getLineColors()
const hashColorDygraphSeries = {}
for (const seriesName in dygraphSeries) {
const series = dygraphSeries[seriesName]
const hashIndex = hasherino(seriesName, colors.length)
const color = colors[hashIndex]
hashColorDygraphSeries[seriesName] = {...series, color}
}
return hashColorDygraphSeries
}
handleHideLegend = e => {
const {top, bottom, left, right} = this.graphRef.getBoundingClientRect()
@ -363,7 +381,7 @@ class Dygraph extends Component {
/>
{staticLegend && (
<StaticLegend
dygraphSeries={this.hashColorDygraphSeries()}
dygraphSeries={this.colorDygraphSeries()}
dygraph={this.dygraph}
handleReceiveStaticLegendHeight={
this.handleReceiveStaticLegendHeight
@ -420,7 +438,12 @@ Dygraph.propTypes = {
isGraphFilled: bool,
isBarGraph: bool,
staticLegend: bool,
overrideLineColors: array,
overrideLineColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
}).isRequired
),
dygraphSeries: shape({}).isRequired,
ruleValues: shape({
operator: string,
@ -437,6 +460,7 @@ Dygraph.propTypes = {
onZoom: func,
mode: string,
children: node,
colors: colorsStringSchema.isRequired,
}
const mapStateToProps = ({annotations: {mode}}) => ({

View File

@ -10,6 +10,8 @@ import {
MIN_THRESHOLDS,
} from 'shared/constants/thresholds'
import {colorsStringSchema} from 'shared/schemas'
class Gauge extends Component {
constructor(props) {
super(props)
@ -325,21 +327,13 @@ class Gauge extends Component {
}
}
const {arrayOf, number, shape, string} = PropTypes
const {number, string} = PropTypes
Gauge.propTypes = {
width: string.isRequired,
height: string.isRequired,
gaugePosition: number.isRequired,
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
).isRequired,
colors: colorsStringSchema.isRequired,
prefix: string.isRequired,
suffix: string.isRequired,
}

View File

@ -7,6 +7,8 @@ import {DEFAULT_GAUGE_COLORS} from 'src/shared/constants/thresholds'
import {stringifyColorValues} from 'src/shared/constants/colorOperations'
import {DASHBOARD_LAYOUT_ROW_HEIGHT} from 'src/shared/constants'
import {colorsStringSchema} from 'shared/schemas'
class GaugeChart extends PureComponent {
render() {
const {
@ -82,15 +84,7 @@ GaugeChart.propTypes = {
cellHeight: number,
resizerTopHeight: number,
resizeCoords: shape(),
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
),
colors: colorsStringSchema,
prefix: string.isRequired,
suffix: string.isRequired,
}

View File

@ -8,6 +8,8 @@ import {IS_STATIC_LEGEND} from 'src/shared/constants'
import _ from 'lodash'
import {colorsStringSchema} from 'shared/schemas'
const getSource = (cell, source, sources, defaultSource) => {
const s = _.get(cell, ['queries', '0', 'source'], null)
if (!s) {
@ -134,15 +136,7 @@ const propTypes = {
i: string.isRequired,
name: string.isRequired,
type: string.isRequired,
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
),
colors: colorsStringSchema,
}).isRequired,
templates: arrayOf(shape()),
host: string,

View File

@ -6,7 +6,7 @@ import shallowCompare from 'react-addons-shallow-compare'
import SingleStat from 'src/shared/components/SingleStat'
import timeSeriesToDygraph from 'utils/timeSeriesTransformers'
import {SINGLE_STAT_LINE_COLORS} from 'src/shared/graphs/helpers'
import {colorsStringSchema} from 'shared/schemas'
class LineGraph extends Component {
constructor(props) {
@ -85,10 +85,6 @@ class LineGraph extends Component {
connectSeparatedPoints: true,
}
const lineColors = showSingleStat
? SINGLE_STAT_LINE_COLORS
: overrideLineColors
const containerStyle = {
width: 'calc(100% - 32px)',
height: 'calc(100% - 16px)',
@ -118,7 +114,8 @@ class LineGraph extends Component {
resizeCoords={resizeCoords}
dygraphSeries={dygraphSeries}
setResolution={setResolution}
overrideLineColors={lineColors}
colors={colors}
overrideLineColors={overrideLineColors}
containerStyle={containerStyle}
staticLegend={staticLegend}
isGraphFilled={showSingleStat ? false : isGraphFilled}
@ -201,15 +198,7 @@ LineGraph.propTypes = {
resizeCoords: shape(),
queries: arrayOf(shape({}).isRequired).isRequired,
data: arrayOf(shape({}).isRequired).isRequired,
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
),
colors: colorsStringSchema,
}
export default LineGraph

View File

@ -0,0 +1,52 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import ColorScaleDropdown from 'shared/components/ColorScaleDropdown'
import {updateLineColors} from 'src/dashboards/actions/cellEditorOverlay'
import {colorsStringSchema} from 'shared/schemas'
class LineGraphColorSelector extends Component {
handleSelectColors = colorScale => {
const {handleUpdateLineColors} = this.props
const {colors} = colorScale
handleUpdateLineColors(colors)
}
render() {
const {lineColors} = this.props
return (
<div className="form-group col-xs-12">
<label>Line Colors</label>
<ColorScaleDropdown
onChoose={this.handleSelectColors}
stretchToFit={true}
selected={lineColors}
/>
</div>
)
}
}
const {func} = PropTypes
LineGraphColorSelector.propTypes = {
lineColors: colorsStringSchema.isRequired,
handleUpdateLineColors: func.isRequired,
}
const mapStateToProps = ({cellEditorOverlay: {lineColors}}) => ({
lineColors,
})
const mapDispatchToProps = dispatch => ({
handleUpdateLineColors: bindActionCreators(updateLineColors, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(
LineGraphColorSelector
)

View File

@ -9,6 +9,8 @@ import SingleStat from 'shared/components/SingleStat'
import GaugeChart from 'shared/components/GaugeChart'
import TableGraph from 'shared/components/TableGraph'
import {colorsStringSchema} from 'shared/schemas'
const RefreshingLineGraph = AutoRefresh(LineGraph)
const RefreshingSingleStat = AutoRefresh(SingleStat)
const RefreshingGaugeChart = AutoRefresh(GaugeChart)
@ -154,15 +156,7 @@ RefreshingGraph.propTypes = {
onZoom: func,
resizeCoords: shape(),
grabDataForDownload: func,
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
),
colors: colorsStringSchema,
cellID: string,
inView: bool,
tableOptions: shape({}),

View File

@ -6,6 +6,7 @@ import lastValues from 'shared/parsing/lastValues'
import {SMALL_CELL_HEIGHT} from 'shared/graphs/helpers'
import {DYGRAPH_CONTAINER_V_MARGIN} from 'shared/constants'
import {generateThresholdsListHexs} from 'shared/constants/colorOperations'
import {colorsStringSchema} from 'shared/schemas'
class SingleStat extends PureComponent {
render() {
@ -78,15 +79,7 @@ SingleStat.propTypes = {
data: arrayOf(shape()).isRequired,
isFetchingInitially: bool,
cellHeight: number,
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
),
colors: colorsStringSchema,
prefix: string,
suffix: string,
lineGraph: bool,

View File

@ -86,10 +86,6 @@ class StaticLegend extends Component {
key={uuid.v4()}
onMouseDown={this.handleClick(i)}
>
<div
className="static-legend--dot"
style={{backgroundColor: colors[i]}}
/>
<span style={{color: colors[i]}}>{removeMeasurement(v)}</span>
</div>
))}

View File

@ -27,6 +27,7 @@ import {
} from 'src/shared/constants/tableGraph'
import {generateThresholdsListHexs} from 'shared/constants/colorOperations'
import {colorsStringSchema} from 'shared/schemas'
class TableGraph extends Component {
constructor(props) {
@ -432,15 +433,7 @@ TableGraph.propTypes = {
}),
hoverTime: string,
onSetHoverTime: func,
colors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
),
colors: colorsStringSchema,
setDataLabels: func,
}

View File

@ -10,6 +10,7 @@ import Threshold from 'src/dashboards/components/Threshold'
import ColorDropdown from 'shared/components/ColorDropdown'
import {updateThresholdsListColors} from 'src/dashboards/actions/cellEditorOverlay'
import {colorsNumberSchema} from 'shared/schemas'
import {
THRESHOLD_COLORS,
@ -166,22 +167,14 @@ class ThresholdsList extends Component {
)
}
}
const {arrayOf, bool, func, number, shape, string} = PropTypes
const {bool, func, string} = PropTypes
ThresholdsList.defaultProps = {
showListHeading: false,
}
ThresholdsList.propTypes = {
thresholdsListType: string.isRequired,
thresholdsListColors: arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: number.isRequired,
}).isRequired
),
thresholdsListColors: colorsNumberSchema.isRequired,
handleUpdateThresholdsListColors: func.isRequired,
onResetFocus: func.isRequired,
showListHeading: bool,

View File

@ -1,36 +1,21 @@
import _ from 'lodash'
import chroma from 'chroma-js'
import {
THRESHOLD_COLORS,
THRESHOLD_TYPE_BASE,
THRESHOLD_TYPE_TEXT,
} from 'shared/constants/thresholds'
const hexToRgb = hex => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null
}
const averageRgbValues = valuesObject => {
const {r, g, b} = valuesObject
return (r + g + b) / 3
}
const trueNeutralGrey = 128
const luminanceThreshold = 0.5
const getLegibleTextColor = bgColorHex => {
const averageBackground = averageRgbValues(hexToRgb(bgColorHex))
const isBackgroundLight = averageBackground > trueNeutralGrey
const darkText = '#292933'
const lightText = '#ffffff'
return isBackgroundLight ? darkText : lightText
return chroma(bgColorHex).luminance() < luminanceThreshold
? darkText
: lightText
}
const findNearestCrossedThreshold = (colors, lastValue) => {

View File

@ -0,0 +1,228 @@
import chroma from 'chroma-js'
const COLOR_TYPE_SCALE = 'scale'
// Color Palettes
export const LINE_COLORS_A = [
{
type: COLOR_TYPE_SCALE,
hex: '#31C0F6',
id: '0',
name: 'Nineteen Eighty Four',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#A500A5',
id: '0',
name: 'Nineteen Eighty Four',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#FF7E27',
id: '0',
name: 'Nineteen Eighty Four',
value: '0',
},
]
export const LINE_COLORS_B = [
{
type: COLOR_TYPE_SCALE,
hex: '#74D495',
id: '1',
name: 'Atlantis',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#3F3FBA',
id: '1',
name: 'Atlantis',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#FF4D9E',
id: '1',
name: 'Atlantis',
value: '0',
},
]
export const LINE_COLORS_C = [
{
type: COLOR_TYPE_SCALE,
hex: '#8F8AF4',
id: '1',
name: 'Do Androids Dream of Electric Sheep?',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#A51414',
id: '1',
name: 'Do Androids Dream of Electric Sheep?',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#F4CF31',
id: '1',
name: 'Do Androids Dream of Electric Sheep?',
value: '0',
},
]
export const LINE_COLORS_D = [
{
type: COLOR_TYPE_SCALE,
hex: '#FD7A5D',
id: '1',
name: 'Delorean',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#5F1CF2',
id: '1',
name: 'Delorean',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#4CE09A',
id: '1',
name: 'Delorean',
value: '0',
},
]
export const LINE_COLORS_E = [
{
type: COLOR_TYPE_SCALE,
hex: '#FDC44F',
id: '1',
name: 'Cthulhu',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#007C76',
id: '1',
name: 'Cthulhu',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#8983FF',
id: '1',
name: 'Cthulhu',
value: '0',
},
]
export const LINE_COLORS_F = [
{
type: COLOR_TYPE_SCALE,
hex: '#DA6FF1',
id: '1',
name: 'Ectoplasm',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#00717A',
id: '1',
name: 'Ectoplasm',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#ACFF76',
id: '1',
name: 'Ectoplasm',
value: '0',
},
]
export const LINE_COLORS_G = [
{
type: COLOR_TYPE_SCALE,
hex: '#F6F6F8',
id: '1',
name: 'T-Max 400 Film',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#A4A8B6',
id: '1',
name: 'T-Max 400 Film',
value: '0',
},
{
type: COLOR_TYPE_SCALE,
hex: '#545667',
id: '1',
name: 'T-Max 400 Film',
value: '0',
},
]
export const LINE_COLORS_RULE_GRAPH = [
{
type: COLOR_TYPE_SCALE,
hex: '#4ED8A0',
id: '1',
name: 'Rainforest',
value: '0',
},
]
export const DEFAULT_LINE_COLORS = LINE_COLORS_A
export const LINE_COLOR_SCALES = [
LINE_COLORS_A,
LINE_COLORS_B,
LINE_COLORS_C,
LINE_COLORS_D,
LINE_COLORS_E,
LINE_COLORS_F,
LINE_COLORS_G,
].map(colorScale => {
const name = colorScale[0].name
const colors = colorScale
const id = colorScale[0].id
return {name, colors, id}
})
export const validateLineColors = colors => {
if (!colors || colors.length === 0) {
return DEFAULT_LINE_COLORS
}
const testColorsTypes =
colors.filter(color => color.type === COLOR_TYPE_SCALE).length ===
colors.length
return testColorsTypes ? colors : DEFAULT_LINE_COLORS
}
export const getLineColorsHexes = (colors, numSeries) => {
const validatedColors = validateLineColors(colors) // ensures safe defaults
const colorsHexArray = validatedColors.map(color => color.hex)
if (numSeries === 1 || numSeries === 0) {
return [colorsHexArray[0]]
}
if (numSeries === 2) {
return [colorsHexArray[0], colorsHexArray[1]]
}
return chroma
.scale(colorsHexArray)
.mode('lch')
.colors(numSeries)
}

View File

@ -1,6 +1,6 @@
import PropTypes from 'prop-types'
const {shape, string} = PropTypes
const {arrayOf, number, shape, string} = PropTypes
export const annotation = shape({
id: string.isRequired,
@ -9,3 +9,23 @@ export const annotation = shape({
text: string.isRequired,
type: string.isRequired,
})
export const colorsStringSchema = arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: string.isRequired,
}).isRequired
)
export const colorsNumberSchema = arrayOf(
shape({
type: string.isRequired,
hex: string.isRequired,
id: string.isRequired,
name: string.isRequired,
value: number.isRequired,
}).isRequired
)

View File

@ -1,3 +1,5 @@
import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes'
export const fixtureStatusPageCells = [
{
i: 'alerts-bar-graph',
@ -8,6 +10,7 @@ export const fixtureStatusPageCells = [
h: 4,
legend: {},
name: 'Alert Events per Day Last 30 Days',
colors: DEFAULT_LINE_COLORS,
queries: [
{
query:

View File

@ -4,6 +4,10 @@
*/
$color-dropdown--circle: 14px;
$color-dropdown--bar: 104px;
$color-dropdown--bar-height: 10px;
$color-dropdown--left-padding: 11px;
$color-dropdown--name-padding: 20px;
.color-dropdown {
width: 140px;
@ -31,11 +35,11 @@ $color-dropdown--circle: 14px;
position: absolute;
top: 30px;
left: 0;
z-index: 2;
z-index: 5;
width: 100%;
border-radius: 4px;
box-shadow: 0 2px 5px 0.6px fade-out($g0-obsidian, 0.7);
@include gradient-h($g0-obsidian,$g2-kevlar);
@include gradient-h($g0-obsidian, $g2-kevlar);
}
.color-dropdown--item {
@include no-user-select();
@ -43,9 +47,7 @@ $color-dropdown--circle: 14px;
height: 28px;
position: relative;
color: $g11-sidewalk;
transition:
color 0.25s ease,
background-color 0.25s ease;
transition: color 0.25s ease, background-color 0.25s ease;
&:hover {
background-color: $g4-onyx;
@ -67,6 +69,7 @@ $color-dropdown--circle: 14px;
}
}
.color-dropdown--swatch,
.color-dropdown--swatches,
.color-dropdown--name {
position: absolute;
top: 50%;
@ -76,21 +79,35 @@ $color-dropdown--circle: 14px;
width: $color-dropdown--circle;
height: $color-dropdown--circle;
border-radius: 50%;
left: 11px;
left: $color-dropdown--left-padding;
}
.color-dropdown--swatches {
width: $color-dropdown--bar;
height: $color-dropdown--bar-height;
border-radius: $color-dropdown--bar-height / 2;
left: $color-dropdown--left-padding;
}
.color-dropdown--name {
text-align: left;
right: 11px;
left: 34px;
right: $color-dropdown--name-padding;
left: $color-dropdown--circle + $color-dropdown--name-padding;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
font-weight: 600;
text-transform: capitalize;
.color-dropdown--swatches + & {
left: $color-dropdown--bar + $color-dropdown--name-padding;
}
}
.color-dropdown .color-dropdown--menu .fancy-scroll--container .fancy-scroll--track-v .fancy-scroll--thumb-v {
@include gradient-v($g9-mountain,$g7-graphite);
.color-dropdown
.color-dropdown--menu
.fancy-scroll--container
.fancy-scroll--track-v
.fancy-scroll--thumb-v {
@include gradient-v($g9-mountain, $g7-graphite);
}
.color-dropdown--toggle.color-dropdown__disabled {
color: $g7-graphite;

View File

@ -16,17 +16,9 @@
flex-wrap: wrap;
max-height: 50%;
overflow: auto;
@include custom-scrollbar($g3-castle,$g6-smoke);
}
.static-legend--dot {
display: inline-block;
vertical-align: middle;
margin-right: 4px;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: $g20-white;
@include custom-scrollbar($g3-castle, $g6-smoke);
}
.static-legend--item,
.static-legend--single {
height: 22px;
@ -43,8 +35,7 @@
.static-legend--item {
transition: background-color 0.25s ease, color 0.25s ease;
span,
.static-legend--dot {
span {
opacity: 0.8;
transition: opacity 0.25s ease;
}
@ -53,8 +44,7 @@
cursor: pointer;
background-color: $g6-smoke;
span,
.static-legend--dot {
span {
opacity: 1;
}
}
@ -62,16 +52,14 @@
background-color: $g1-raven;
font-style: italic;
span,
.static-legend--dot {
span {
opacity: 0.35;
}
&:hover {
background-color: $g2-kevlar;
span,
.static-legend--dot {
span {
opacity: 0.65;
}
}

View File

@ -16,9 +16,7 @@
.tab {
font-weight: 500 !important;
border-radius: $radius $radius 0 0 !important;
transition:
background-color 0.25s ease,
color 0.25s ease !important;
transition: background-color 0.25s ease, color 0.25s ease !important;
border: 0 !important;
text-align: left;
height: 60px !important;
@ -41,16 +39,24 @@
}
}
.admin-tabs--content {
.panel {border-top-left-radius: 0;}
.panel-heading {height: 60px;}
.panel {
border-top-left-radius: 0;
}
.panel-heading {
height: 60px;
}
.panel-title {
font-size: 17px;
font-weight: 400 !important;
color: $g12-forge;
padding: 6px 0;
}
.panel-body {min-height: 300px;}
.panel-heading + .panel-body {padding-top: 0;}
.panel-body {
min-height: 300px;
}
.panel-heading + .panel-body {
padding-top: 0;
}
}
/*
@ -69,7 +75,9 @@
border-radius: $radius 0 0 $radius !important;
padding: 0 0 0 16px !important;
}
& + div {padding-left: 0;}
& + div {
padding-left: 0;
}
}
}
@ -82,8 +90,13 @@
font-weight: 600;
color: $g14-chromium;
transition: none !important;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
.caret {opacity: 0;}
.caret {
opacity: 0;
}
}
.admin-table--multi-select-empty .dropdown-toggle {
color: $g8-storm;
@ -92,7 +105,9 @@
color: $g20-white !important;
background-color: $c-pool;
.caret {opacity: 1;}
.caret {
opacity: 1;
}
&:hover {
transition: background-color 0.25s ease;
@ -100,7 +115,9 @@
}
}
table > tbody > tr > td.admin-table--left-offset,
table > thead > tr > th.admin-table--left-offset {padding-left: 15px;}
table > thead > tr > th.admin-table--left-offset {
padding-left: 15px;
}
table > tbody > tr.admin-table--edit-row,
table > tbody > tr.admin-table--edit-row:hover,
@ -141,9 +158,15 @@ pre.admin-table--query {
.db-manager {
margin-bottom: 8px;
&:last-child {margin-bottom: 0;}
.db-manager-header--actions {visibility: hidden;}
&:hover .db-manager-header--actions {visibility: visible;}
&:last-child {
margin-bottom: 0;
}
.db-manager-header--actions {
visibility: hidden;
}
&:hover .db-manager-header--actions {
visibility: visible;
}
}
.db-manager-header {
padding: 0 11px;
@ -182,7 +205,9 @@ pre.admin-table--query {
padding: 9px 11px;
border-radius: 0 0 $radius-small $radius-small;
.table-highlight > tbody > tr:hover {background-color: $g5-pepper;}
.table-highlight > tbody > tr:hover {
background-color: $g5-pepper;
}
}
/*

View File

@ -0,0 +1,14 @@
export interface UserRole {
name: string
}
interface UserPermission {
name: string
}
export interface User {
name: string
roles: UserRole[]
permissions: UserPermission[]
password: string
}

View File

@ -8,6 +8,7 @@ import {
updateThresholdsListColors,
updateThresholdsListType,
updateGaugeColors,
updateLineColors,
updateAxes,
} from 'src/dashboards/actions/cellEditorOverlay'
import {DEFAULT_TABLE_OPTIONS} from 'src/shared/constants/tableGraph'
@ -17,6 +18,7 @@ import {
validateThresholdsListColors,
getThresholdsListType,
} from 'shared/constants/thresholds'
import {validateLineColors} from 'src/shared/constants/graphColorPalettes'
const defaultCellType = 'line'
const defaultCellName = 'defaultCell'
@ -45,6 +47,7 @@ const defaultThresholdsListColors = validateThresholdsListColors(
defaultThresholdsListType
)
const defaultGaugeColors = validateGaugeColors(defaultCell.colors)
const defaultLineColors = validateLineColors(defaultCell.colors)
describe('Dashboards.Reducers.cellEditorOverlay', () => {
it('should show cell editor overlay', () => {
@ -117,4 +120,11 @@ describe('Dashboards.Reducers.cellEditorOverlay', () => {
expect(actual.cell.axes).toBe(expected)
})
it('should update the cell line graph colors', () => {
const actual = reducer(initialState, updateLineColors(defaultLineColors))
const expected = defaultLineColors
expect(actual.lineColors).toBe(expected)
})
})

View File

@ -1686,6 +1686,10 @@ chownr@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
chroma-js@^1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-1.3.6.tgz#22dd7220ef6b55dcfcb8ef92982baaf55dced45d"
ci-info@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4"