mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge
This commit is contained in:
commit
d97cd450c9
@ -51,7 +51,7 @@ When a user creates a new dashboard, a new dashboard JSON object is initialized
|
|||||||
"list": []
|
"list": []
|
||||||
},
|
},
|
||||||
"refresh": "5s",
|
"refresh": "5s",
|
||||||
"schemaVersion": 16,
|
"schemaVersion": 17,
|
||||||
"version": 0,
|
"version": 0,
|
||||||
"links": []
|
"links": []
|
||||||
}
|
}
|
||||||
|
@ -292,9 +292,11 @@ The `direction` controls how the panels will be arranged.
|
|||||||
|
|
||||||
By choosing `horizontal` the panels will be arranged side-by-side. Grafana will automatically adjust the width
|
By choosing `horizontal` the panels will be arranged side-by-side. Grafana will automatically adjust the width
|
||||||
of each repeated panel so that the whole row is filled. Currently, you cannot mix other panels on a row with a repeated
|
of each repeated panel so that the whole row is filled. Currently, you cannot mix other panels on a row with a repeated
|
||||||
panel. Each panel will never be smaller that the provided `Min width` if you have many selected values.
|
panel.
|
||||||
|
|
||||||
By choosing `vertical` the panels will be arranged from top to bottom in a column. The `Min width` doesn't have any effect in this case. The width of the repeated panels will be the same as of the first panel (the original template) being repeated.
|
Set `Max per row` to tell grafana how many panels per row you want at most. It defaults to *4* if you don't set anything.
|
||||||
|
|
||||||
|
By choosing `vertical` the panels will be arranged from top to bottom in a column. The width of the repeated panels will be the same as of the first panel (the original template) being repeated.
|
||||||
|
|
||||||
Only make changes to the first panel (the original template). To have the changes take effect on all panels you need to trigger a dynamic dashboard re-build.
|
Only make changes to the first panel (the original template). To have the changes take effect on all panels you need to trigger a dynamic dashboard re-build.
|
||||||
You can do this by either changing the variable value (that is the basis for the repeat) or reload the dashboard.
|
You can do this by either changing the variable value (that is the basis for the repeat) or reload the dashboard.
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
import React, { SFC } from 'react';
|
||||||
|
|
||||||
|
interface LoadingPlaceholderProps {
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoadingPlaceholder: SFC<LoadingPlaceholderProps> = ({ text }) => (
|
||||||
|
<div className="gf-form-group">
|
||||||
|
{text} <i className="fa fa-spinner fa-spin" />
|
||||||
|
</div>
|
||||||
|
);
|
@ -1,7 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||||
|
// @ts-ignore
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
|
|
||||||
export const IndicatorsContainer = props => {
|
export const IndicatorsContainer = (props: any) => {
|
||||||
const isOpen = props.selectProps.menuIsOpen;
|
const isOpen = props.selectProps.menuIsOpen;
|
||||||
return (
|
return (
|
||||||
<components.IndicatorsContainer {...props}>
|
<components.IndicatorsContainer {...props}>
|
@ -1,5 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||||
|
// @ts-ignore
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
|
// @ts-ignore
|
||||||
import { OptionProps } from '@torkelo/react-select/lib/components/Option';
|
import { OptionProps } from '@torkelo/react-select/lib/components/Option';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
@ -1,16 +1,21 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||||
|
// @ts-ignore
|
||||||
import { default as ReactSelect } from '@torkelo/react-select';
|
import { default as ReactSelect } from '@torkelo/react-select';
|
||||||
|
// @ts-ignore
|
||||||
import { default as ReactAsyncSelect } from '@torkelo/react-select/lib/Async';
|
import { default as ReactAsyncSelect } from '@torkelo/react-select/lib/Async';
|
||||||
|
// @ts-ignore
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Option, SingleValue } from './PickerOption';
|
import { SelectOption, SingleValue } from './SelectOption';
|
||||||
import OptionGroup from './OptionGroup';
|
import SelectOptionGroup from './SelectOptionGroup';
|
||||||
import IndicatorsContainer from './IndicatorsContainer';
|
import IndicatorsContainer from './IndicatorsContainer';
|
||||||
import NoOptionsMessage from './NoOptionsMessage';
|
import NoOptionsMessage from './NoOptionsMessage';
|
||||||
import ResetStyles from './ResetStyles';
|
import resetSelectStyles from './resetSelectStyles';
|
||||||
import { CustomScrollbar } from '@grafana/ui';
|
import { CustomScrollbar } from '@grafana/ui';
|
||||||
|
|
||||||
export interface SelectOptionItem {
|
export interface SelectOptionItem {
|
||||||
@ -53,7 +58,7 @@ interface AsyncProps {
|
|||||||
loadingMessage?: () => string;
|
loadingMessage?: () => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MenuList = props => {
|
export const MenuList = (props: any) => {
|
||||||
return (
|
return (
|
||||||
<components.MenuList {...props}>
|
<components.MenuList {...props}>
|
||||||
<CustomScrollbar autoHide={false}>{props.children}</CustomScrollbar>
|
<CustomScrollbar autoHide={false}>{props.children}</CustomScrollbar>
|
||||||
@ -112,11 +117,11 @@ export class Select extends PureComponent<CommonProps & SelectProps> {
|
|||||||
classNamePrefix="gf-form-select-box"
|
classNamePrefix="gf-form-select-box"
|
||||||
className={selectClassNames}
|
className={selectClassNames}
|
||||||
components={{
|
components={{
|
||||||
Option,
|
Option: SelectOption,
|
||||||
SingleValue,
|
SingleValue,
|
||||||
IndicatorsContainer,
|
IndicatorsContainer,
|
||||||
MenuList,
|
MenuList,
|
||||||
Group: OptionGroup,
|
Group: SelectOptionGroup,
|
||||||
}}
|
}}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
value={value}
|
value={value}
|
||||||
@ -127,7 +132,7 @@ export class Select extends PureComponent<CommonProps & SelectProps> {
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={options}
|
options={options}
|
||||||
placeholder={placeholder || 'Choose'}
|
placeholder={placeholder || 'Choose'}
|
||||||
styles={ResetStyles}
|
styles={resetSelectStyles()}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isClearable={isClearable}
|
isClearable={isClearable}
|
||||||
@ -212,7 +217,7 @@ export class AsyncSelect extends PureComponent<CommonProps & AsyncProps> {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
defaultOptions={defaultOptions}
|
defaultOptions={defaultOptions}
|
||||||
placeholder={placeholder || 'Choose'}
|
placeholder={placeholder || 'Choose'}
|
||||||
styles={ResetStyles}
|
styles={resetSelectStyles()}
|
||||||
loadingMessage={loadingMessage}
|
loadingMessage={loadingMessage}
|
||||||
noOptionsMessage={noOptionsMessage}
|
noOptionsMessage={noOptionsMessage}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
@ -1,11 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import PickerOption from './PickerOption';
|
import SelectOption from './SelectOption';
|
||||||
|
import { OptionProps } from 'react-select/lib/components/Option';
|
||||||
|
|
||||||
const model = {
|
const model: OptionProps<any> = {
|
||||||
cx: jest.fn(),
|
cx: jest.fn(),
|
||||||
clearValue: jest.fn(),
|
clearValue: jest.fn(),
|
||||||
onSelect: jest.fn(),
|
|
||||||
getStyles: jest.fn(),
|
getStyles: jest.fn(),
|
||||||
getValue: jest.fn(),
|
getValue: jest.fn(),
|
||||||
hasValue: true,
|
hasValue: true,
|
||||||
@ -18,21 +18,31 @@ const model = {
|
|||||||
isFocused: false,
|
isFocused: false,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
innerRef: null,
|
innerRef: null,
|
||||||
innerProps: null,
|
innerProps: {
|
||||||
label: 'Option label',
|
id: '',
|
||||||
type: null,
|
key: '',
|
||||||
children: 'Model title',
|
onClick: jest.fn(),
|
||||||
data: {
|
onMouseOver: jest.fn(),
|
||||||
title: 'Model title',
|
tabIndex: 1,
|
||||||
imgUrl: 'url/to/avatar',
|
|
||||||
label: 'User picker label',
|
|
||||||
},
|
},
|
||||||
|
label: 'Option label',
|
||||||
|
type: 'option',
|
||||||
|
children: 'Model title',
|
||||||
className: 'class-for-user-picker',
|
className: 'class-for-user-picker',
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('PickerOption', () => {
|
describe('SelectOption', () => {
|
||||||
it('renders correctly', () => {
|
it('renders correctly', () => {
|
||||||
const tree = renderer.create(<PickerOption {...model} />).toJSON();
|
const tree = renderer
|
||||||
|
.create(
|
||||||
|
<SelectOption
|
||||||
|
{...model}
|
||||||
|
data={{
|
||||||
|
imgUrl: 'url/to/avatar',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
.toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -1,4 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
|
||||||
|
// @ts-ignore
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
import { OptionProps } from 'react-select/lib/components/Option';
|
import { OptionProps } from 'react-select/lib/components/Option';
|
||||||
|
|
||||||
@ -10,7 +13,7 @@ interface ExtendedOptionProps extends OptionProps<any> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Option = (props: ExtendedOptionProps) => {
|
export const SelectOption = (props: ExtendedOptionProps) => {
|
||||||
const { children, isSelected, data } = props;
|
const { children, isSelected, data } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -28,7 +31,7 @@ export const Option = (props: ExtendedOptionProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// was not able to type this without typescript error
|
// was not able to type this without typescript error
|
||||||
export const SingleValue = props => {
|
export const SingleValue = (props: any) => {
|
||||||
const { children, data } = props;
|
const { children, data } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -41,4 +44,4 @@ export const SingleValue = props => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Option;
|
export default SelectOption;
|
@ -9,7 +9,7 @@ interface State {
|
|||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class OptionGroup extends PureComponent<ExtendedGroupProps, State> {
|
export default class SelectOptionGroup extends PureComponent<ExtendedGroupProps, State> {
|
||||||
state = {
|
state = {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
};
|
};
|
||||||
@ -24,7 +24,7 @@ export default class OptionGroup extends PureComponent<ExtendedGroupProps, State
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(nextProps) {
|
componentDidUpdate(nextProps: ExtendedGroupProps) {
|
||||||
if (nextProps.selectProps.inputValue !== '') {
|
if (nextProps.selectProps.inputValue !== '') {
|
||||||
this.setState({ expanded: true });
|
this.setState({ expanded: true });
|
||||||
}
|
}
|
@ -1,7 +1,12 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`PickerOption renders correctly 1`] = `
|
exports[`SelectOption renders correctly 1`] = `
|
||||||
<div>
|
<div
|
||||||
|
id=""
|
||||||
|
onClick={[MockFunction]}
|
||||||
|
onMouseOver={[MockFunction]}
|
||||||
|
tabIndex={1}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="gf-form-select-box__desc-option"
|
className="gf-form-select-box__desc-option"
|
||||||
>
|
>
|
@ -0,0 +1,27 @@
|
|||||||
|
export default function resetSelectStyles() {
|
||||||
|
return {
|
||||||
|
clearIndicator: () => ({}),
|
||||||
|
container: () => ({}),
|
||||||
|
control: () => ({}),
|
||||||
|
dropdownIndicator: () => ({}),
|
||||||
|
group: () => ({}),
|
||||||
|
groupHeading: () => ({}),
|
||||||
|
indicatorsContainer: () => ({}),
|
||||||
|
indicatorSeparator: () => ({}),
|
||||||
|
input: () => ({}),
|
||||||
|
loadingIndicator: () => ({}),
|
||||||
|
loadingMessage: () => ({}),
|
||||||
|
menu: () => ({}),
|
||||||
|
menuList: ({ maxHeight }: { maxHeight: number }) => ({
|
||||||
|
maxHeight,
|
||||||
|
}),
|
||||||
|
multiValue: () => ({}),
|
||||||
|
multiValueLabel: () => ({}),
|
||||||
|
multiValueRemove: () => ({}),
|
||||||
|
noOptionsMessage: () => ({}),
|
||||||
|
option: () => ({}),
|
||||||
|
placeholder: () => ({}),
|
||||||
|
singleValue: () => ({}),
|
||||||
|
valueContainer: () => ({}),
|
||||||
|
};
|
||||||
|
}
|
@ -2,3 +2,4 @@
|
|||||||
@import 'DeleteButton/DeleteButton';
|
@import 'DeleteButton/DeleteButton';
|
||||||
@import 'ThresholdsEditor/ThresholdsEditor';
|
@import 'ThresholdsEditor/ThresholdsEditor';
|
||||||
@import 'Tooltip/Tooltip';
|
@import 'Tooltip/Tooltip';
|
||||||
|
@import 'Select/Select';
|
||||||
|
@ -2,6 +2,14 @@ export { DeleteButton } from './DeleteButton/DeleteButton';
|
|||||||
export { Tooltip } from './Tooltip/Tooltip';
|
export { Tooltip } from './Tooltip/Tooltip';
|
||||||
export { Portal } from './Portal/Portal';
|
export { Portal } from './Portal/Portal';
|
||||||
export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
|
export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar';
|
||||||
|
|
||||||
|
// Select
|
||||||
|
export { Select, AsyncSelect, SelectOptionItem } from './Select/Select';
|
||||||
|
export { IndicatorsContainer } from './Select/IndicatorsContainer';
|
||||||
|
export { NoOptionsMessage } from './Select/NoOptionsMessage';
|
||||||
|
export { default as resetSelectStyles } from './Select/resetSelectStyles';
|
||||||
|
|
||||||
|
export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder';
|
||||||
export { ColorPicker } from './ColorPicker/ColorPicker';
|
export { ColorPicker } from './ColorPicker/ColorPicker';
|
||||||
export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover';
|
export { SeriesColorPickerPopover } from './ColorPicker/SeriesColorPickerPopover';
|
||||||
export { SeriesColorPicker } from './ColorPicker/SeriesColorPicker';
|
export { SeriesColorPicker } from './ColorPicker/SeriesColorPicker';
|
||||||
|
@ -112,7 +112,7 @@ func NewDashboard(title string) *Dashboard {
|
|||||||
func NewDashboardFolder(title string) *Dashboard {
|
func NewDashboardFolder(title string) *Dashboard {
|
||||||
folder := NewDashboard(title)
|
folder := NewDashboard(title)
|
||||||
folder.IsFolder = true
|
folder.IsFolder = true
|
||||||
folder.Data.Set("schemaVersion", 16)
|
folder.Data.Set("schemaVersion", 17)
|
||||||
folder.Data.Set("version", 0)
|
folder.Data.Set("version", 0)
|
||||||
folder.IsFolder = true
|
folder.IsFolder = true
|
||||||
return folder
|
return folder
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { UserPicker } from 'app/core/components/Select/UserPicker';
|
import { UserPicker } from 'app/core/components/Select/UserPicker';
|
||||||
import { TeamPicker, Team } from 'app/core/components/Select/TeamPicker';
|
import { TeamPicker, Team } from 'app/core/components/Select/TeamPicker';
|
||||||
import { Select, SelectOptionItem } from 'app/core/components/Select/Select';
|
import { Select, SelectOptionItem } from '@grafana/ui';
|
||||||
import { User } from 'app/types';
|
import { User } from 'app/types';
|
||||||
import {
|
import {
|
||||||
dashboardPermissionLevels,
|
dashboardPermissionLevels,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Select from 'app/core/components/Select/Select';
|
import { Select } from '@grafana/ui';
|
||||||
import { dashboardPermissionLevels } from 'app/types/acl';
|
import { dashboardPermissionLevels } from 'app/types/acl';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { Select } from 'app/core/components/Select/Select';
|
import { Select } from '@grafana/ui';
|
||||||
import { dashboardPermissionLevels, DashboardAcl, PermissionLevel } from 'app/types/acl';
|
import { dashboardPermissionLevels, DashboardAcl, PermissionLevel } from 'app/types/acl';
|
||||||
import { FolderInfo } from 'app/types';
|
import { FolderInfo } from 'app/types';
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Select from './Select';
|
import { Select } from '@grafana/ui';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { DataSourceSelectItem } from 'app/types';
|
import { DataSourceSelectItem } from 'app/types';
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
export default {
|
|
||||||
clearIndicator: () => ({}),
|
|
||||||
container: () => ({}),
|
|
||||||
control: () => ({}),
|
|
||||||
dropdownIndicator: () => ({}),
|
|
||||||
group: () => ({}),
|
|
||||||
groupHeading: () => ({}),
|
|
||||||
indicatorsContainer: () => ({}),
|
|
||||||
indicatorSeparator: () => ({}),
|
|
||||||
input: () => ({}),
|
|
||||||
loadingIndicator: () => ({}),
|
|
||||||
loadingMessage: () => ({}),
|
|
||||||
menu: () => ({}),
|
|
||||||
menuList: ({ maxHeight }: { maxHeight: number }) => ({
|
|
||||||
maxHeight,
|
|
||||||
}),
|
|
||||||
multiValue: () => ({}),
|
|
||||||
multiValueLabel: () => ({}),
|
|
||||||
multiValueRemove: () => ({}),
|
|
||||||
noOptionsMessage: () => ({}),
|
|
||||||
option: () => ({}),
|
|
||||||
placeholder: () => ({}),
|
|
||||||
singleValue: () => ({}),
|
|
||||||
valueContainer: () => ({}),
|
|
||||||
};
|
|
@ -1,6 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { AsyncSelect } from './Select';
|
import { AsyncSelect } from '@grafana/ui';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import Select from './Select';
|
import { Select } from '@grafana/ui';
|
||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -3,7 +3,7 @@ import React, { Component } from 'react';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { AsyncSelect } from './Select';
|
import { AsyncSelect } from '@grafana/ui';
|
||||||
|
|
||||||
// Utils & Services
|
// Utils & Services
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
import { Label } from 'app/core/components/Label/Label';
|
import { Label } from 'app/core/components/Label/Label';
|
||||||
import Select from 'app/core/components/Select/Select';
|
import { Select } from '@grafana/ui';
|
||||||
import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
|
||||||
|
|
||||||
import { DashboardSearchHit } from 'app/types';
|
import { DashboardSearchHit } from 'app/types';
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { NoOptionsMessage, IndicatorsContainer, resetSelectStyles } from '@grafana/ui';
|
||||||
import AsyncSelect from '@torkelo/react-select/lib/Async';
|
import AsyncSelect from '@torkelo/react-select/lib/Async';
|
||||||
|
|
||||||
import { TagOption } from './TagOption';
|
import { TagOption } from './TagOption';
|
||||||
import { TagBadge } from './TagBadge';
|
import { TagBadge } from './TagBadge';
|
||||||
import IndicatorsContainer from 'app/core/components/Select/IndicatorsContainer';
|
|
||||||
import NoOptionsMessage from 'app/core/components/Select/NoOptionsMessage';
|
|
||||||
import { components } from '@torkelo/react-select';
|
import { components } from '@torkelo/react-select';
|
||||||
import ResetStyles from 'app/core/components/Select/ResetStyles';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
tags: string[];
|
tags: string[];
|
||||||
@ -51,7 +49,7 @@ export class TagFilter extends React.Component<Props, any> {
|
|||||||
getOptionValue: i => i.value,
|
getOptionValue: i => i.value,
|
||||||
getOptionLabel: i => i.label,
|
getOptionLabel: i => i.label,
|
||||||
value: tags,
|
value: tags,
|
||||||
styles: ResetStyles,
|
styles: resetSelectStyles(),
|
||||||
filterOption: (option, searchQuery) => {
|
filterOption: (option, searchQuery) => {
|
||||||
const regex = RegExp(searchQuery, 'i');
|
const regex = RegExp(searchQuery, 'i');
|
||||||
return regex.test(option.value);
|
return regex.test(option.value);
|
||||||
|
8
public/app/core/specs/factors.test.ts
Normal file
8
public/app/core/specs/factors.test.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import getFactors from 'app/core/utils/factors';
|
||||||
|
|
||||||
|
describe('factors', () => {
|
||||||
|
it('should return factors for 12', () => {
|
||||||
|
const factors = getFactors(12);
|
||||||
|
expect(factors).toEqual([1, 2, 3, 4, 6, 12]);
|
||||||
|
});
|
||||||
|
});
|
5
public/app/core/utils/factors.ts
Normal file
5
public/app/core/utils/factors.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Returns the factors of a number
|
||||||
|
// Example getFactors(12) -> [1, 2, 3, 4, 6, 12]
|
||||||
|
export default function getFactors(num: number): number[] {
|
||||||
|
return Array.from(new Array(num + 1), (_, i) => i).filter(i => num % i === 0);
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import React, { PureComponent, SFC } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
// Services & Utils
|
// Services & Utils
|
||||||
import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader';
|
import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader';
|
||||||
@ -14,7 +14,7 @@ import 'app/features/alerting/AlertTabCtrl';
|
|||||||
// Types
|
// Types
|
||||||
import { DashboardModel } from '../dashboard/dashboard_model';
|
import { DashboardModel } from '../dashboard/dashboard_model';
|
||||||
import { PanelModel } from '../dashboard/panel_model';
|
import { PanelModel } from '../dashboard/panel_model';
|
||||||
import { TestRuleButton } from './TestRuleButton';
|
import { TestRuleResult } from './TestRuleResult';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
angularPanel?: AngularComponent;
|
angularPanel?: AngularComponent;
|
||||||
@ -22,16 +22,6 @@ interface Props {
|
|||||||
panel: PanelModel;
|
panel: PanelModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LoadingPlaceholderProps {
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LoadingPlaceholder: SFC<LoadingPlaceholderProps> = ({ text }) => (
|
|
||||||
<div className="gf-form-group">
|
|
||||||
{text} <i className="fa fa-spinner fa-spin" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export class AlertTab extends PureComponent<Props> {
|
export class AlertTab extends PureComponent<Props> {
|
||||||
element: any;
|
element: any;
|
||||||
component: AngularComponent;
|
component: AngularComponent;
|
||||||
@ -120,14 +110,14 @@ export class AlertTab extends PureComponent<Props> {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
renderTestRuleButton = () => {
|
renderTestRuleResult = () => {
|
||||||
const { panel, dashboard } = this.props;
|
const { panel, dashboard } = this.props;
|
||||||
return <TestRuleButton panelId={panel.id} dashboard={dashboard} LoadingPlaceholder={LoadingPlaceholder} />;
|
return <TestRuleResult panelId={panel.id} dashboard={dashboard} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
testRule = (): EditorToolbarView => ({
|
testRule = (): EditorToolbarView => ({
|
||||||
title: 'Test Rule',
|
title: 'Test Rule',
|
||||||
render: () => this.renderTestRuleButton(),
|
render: () => this.renderTestRuleResult(),
|
||||||
});
|
});
|
||||||
|
|
||||||
onAddAlert = () => {
|
onAddAlert = () => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import { DashboardModel } from '../dashboard/dashboard_model';
|
import { DashboardModel } from '../dashboard/dashboard_model';
|
||||||
import { Props, TestRuleButton } from './TestRuleButton';
|
import { Props, TestRuleResult } from './TestRuleResult';
|
||||||
|
|
||||||
jest.mock('app/core/services/backend_srv', () => ({
|
jest.mock('app/core/services/backend_srv', () => ({
|
||||||
getBackendSrv: () => ({
|
getBackendSrv: () => ({
|
||||||
@ -13,14 +13,13 @@ const setup = (propOverrides?: object) => {
|
|||||||
const props: Props = {
|
const props: Props = {
|
||||||
panelId: 1,
|
panelId: 1,
|
||||||
dashboard: new DashboardModel({ panels: [{ id: 1 }] }),
|
dashboard: new DashboardModel({ panels: [{ id: 1 }] }),
|
||||||
LoadingPlaceholder: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(props, propOverrides);
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
const wrapper = shallow(<TestRuleButton {...props} />);
|
const wrapper = shallow(<TestRuleResult {...props} />);
|
||||||
|
|
||||||
return { wrapper, instance: wrapper.instance() as TestRuleButton };
|
return { wrapper, instance: wrapper.instance() as TestRuleResult };
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Render', () => {
|
describe('Render', () => {
|
@ -2,11 +2,11 @@ import React, { PureComponent } from 'react';
|
|||||||
import { JSONFormatter } from 'app/core/components/JSONFormatter/JSONFormatter';
|
import { JSONFormatter } from 'app/core/components/JSONFormatter/JSONFormatter';
|
||||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { DashboardModel } from '../dashboard/dashboard_model';
|
import { DashboardModel } from '../dashboard/dashboard_model';
|
||||||
|
import { LoadingPlaceholder } from '@grafana/ui/src';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
panelId: number;
|
panelId: number;
|
||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
LoadingPlaceholder: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@ -14,7 +14,7 @@ interface State {
|
|||||||
testRuleResponse: {};
|
testRuleResponse: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TestRuleButton extends PureComponent<Props, State> {
|
export class TestRuleResult extends PureComponent<Props, State> {
|
||||||
readonly state: State = {
|
readonly state: State = {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
testRuleResponse: {},
|
testRuleResponse: {},
|
||||||
@ -27,13 +27,14 @@ export class TestRuleButton extends PureComponent<Props, State> {
|
|||||||
async testRule() {
|
async testRule() {
|
||||||
const { panelId, dashboard } = this.props;
|
const { panelId, dashboard } = this.props;
|
||||||
const payload = { dashboard: dashboard.getSaveModelClone(), panelId };
|
const payload = { dashboard: dashboard.getSaveModelClone(), panelId };
|
||||||
|
|
||||||
|
this.setState({ isLoading: true });
|
||||||
const testRuleResponse = await getBackendSrv().post(`/api/alerts/test`, payload);
|
const testRuleResponse = await getBackendSrv().post(`/api/alerts/test`, payload);
|
||||||
this.setState(prevState => ({ ...prevState, isLoading: false, testRuleResponse }));
|
this.setState({ isLoading: false, testRuleResponse });
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { testRuleResponse, isLoading } = this.state;
|
const { testRuleResponse, isLoading } = this.state;
|
||||||
const { LoadingPlaceholder } = this.props;
|
|
||||||
|
|
||||||
if (isLoading === true) {
|
if (isLoading === true) {
|
||||||
return <LoadingPlaceholder text="Evaluating rule" />;
|
return <LoadingPlaceholder text="Evaluating rule" />;
|
@ -1,13 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`Render should render component 1`] = `
|
|
||||||
<JSONFormatter
|
|
||||||
config={
|
|
||||||
Object {
|
|
||||||
"animateOpen": true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
json={Object {}}
|
|
||||||
open={3}
|
|
||||||
/>
|
|
||||||
`;
|
|
@ -0,0 +1,7 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Render should render component 1`] = `
|
||||||
|
<Component
|
||||||
|
text="Evaluating rule"
|
||||||
|
/>
|
||||||
|
`;
|
@ -9,6 +9,7 @@ import {
|
|||||||
} from 'app/core/constants';
|
} from 'app/core/constants';
|
||||||
import { PanelModel } from './panel_model';
|
import { PanelModel } from './panel_model';
|
||||||
import { DashboardModel } from './dashboard_model';
|
import { DashboardModel } from './dashboard_model';
|
||||||
|
import getFactors from 'app/core/utils/factors';
|
||||||
|
|
||||||
export class DashboardMigrator {
|
export class DashboardMigrator {
|
||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
@ -21,7 +22,7 @@ export class DashboardMigrator {
|
|||||||
let i, j, k, n;
|
let i, j, k, n;
|
||||||
const oldVersion = this.dashboard.schemaVersion;
|
const oldVersion = this.dashboard.schemaVersion;
|
||||||
const panelUpgrades = [];
|
const panelUpgrades = [];
|
||||||
this.dashboard.schemaVersion = 16;
|
this.dashboard.schemaVersion = 17;
|
||||||
|
|
||||||
if (oldVersion === this.dashboard.schemaVersion) {
|
if (oldVersion === this.dashboard.schemaVersion) {
|
||||||
return;
|
return;
|
||||||
@ -368,6 +369,24 @@ export class DashboardMigrator {
|
|||||||
this.upgradeToGridLayout(old);
|
this.upgradeToGridLayout(old);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 17) {
|
||||||
|
panelUpgrades.push(panel => {
|
||||||
|
if (panel.minSpan) {
|
||||||
|
const max = GRID_COLUMN_COUNT / panel.minSpan;
|
||||||
|
const factors = getFactors(GRID_COLUMN_COUNT);
|
||||||
|
// find the best match compared to factors
|
||||||
|
// (ie. [1,2,3,4,6,12,24] for 24 columns)
|
||||||
|
panel.maxPerRow =
|
||||||
|
factors[
|
||||||
|
_.findIndex(factors, o => {
|
||||||
|
return o > max;
|
||||||
|
}) - 1
|
||||||
|
];
|
||||||
|
}
|
||||||
|
delete panel.minSpan;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (panelUpgrades.length === 0) {
|
if (panelUpgrades.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -442,7 +442,7 @@ export class DashboardModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectedOptions = this.getSelectedVariableOptions(variable);
|
const selectedOptions = this.getSelectedVariableOptions(variable);
|
||||||
const minWidth = panel.minSpan || 6;
|
const maxPerRow = panel.maxPerRow || 4;
|
||||||
let xPos = 0;
|
let xPos = 0;
|
||||||
let yPos = panel.gridPos.y;
|
let yPos = panel.gridPos.y;
|
||||||
|
|
||||||
@ -462,7 +462,7 @@ export class DashboardModel {
|
|||||||
} else {
|
} else {
|
||||||
// set width based on how many are selected
|
// set width based on how many are selected
|
||||||
// assumed the repeated panels should take up full row width
|
// assumed the repeated panels should take up full row width
|
||||||
copy.gridPos.w = Math.max(GRID_COLUMN_COUNT / selectedOptions.length, minWidth);
|
copy.gridPos.w = Math.max(GRID_COLUMN_COUNT / selectedOptions.length, GRID_COLUMN_COUNT / maxPerRow);
|
||||||
copy.gridPos.x = xPos;
|
copy.gridPos.x = xPos;
|
||||||
copy.gridPos.y = yPos;
|
copy.gridPos.y = yPos;
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import React, { PureComponent, SFC } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import 'app/features/panel/metrics_tab';
|
import 'app/features/panel/metrics_tab';
|
||||||
import { EditorTabBody, EditorToolbarView} from './EditorTabBody';
|
import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
|
||||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||||
import { QueryInspector } from './QueryInspector';
|
import { QueryInspector } from './QueryInspector';
|
||||||
import { QueryOptions } from './QueryOptions';
|
import { QueryOptions } from './QueryOptions';
|
||||||
@ -36,12 +36,6 @@ interface State {
|
|||||||
isAddingMixed: boolean;
|
isAddingMixed: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LoadingPlaceholderProps {
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LoadingPlaceholder: SFC<LoadingPlaceholderProps> = ({ text }) => <h2>{text}</h2>;
|
|
||||||
|
|
||||||
export class QueriesTab extends PureComponent<Props, State> {
|
export class QueriesTab extends PureComponent<Props, State> {
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
component: AngularComponent;
|
component: AngularComponent;
|
||||||
@ -134,7 +128,7 @@ export class QueriesTab extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
renderQueryInspector = () => {
|
renderQueryInspector = () => {
|
||||||
const { panel } = this.props;
|
const { panel } = this.props;
|
||||||
return <QueryInspector panel={panel} LoadingPlaceholder={LoadingPlaceholder} />;
|
return <QueryInspector panel={panel} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderHelp = () => {
|
renderHelp = () => {
|
||||||
|
@ -2,6 +2,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import { JSONFormatter } from 'app/core/components/JSONFormatter/JSONFormatter';
|
import { JSONFormatter } from 'app/core/components/JSONFormatter/JSONFormatter';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
|
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
|
||||||
|
import { LoadingPlaceholder } from '@grafana/ui';
|
||||||
|
|
||||||
interface DsQuery {
|
interface DsQuery {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
@ -10,7 +11,6 @@ interface DsQuery {
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
panel: any;
|
panel: any;
|
||||||
LoadingPlaceholder: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@ -177,7 +177,6 @@ export class QueryInspector extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { response, isLoading } = this.state.dsQuery;
|
const { response, isLoading } = this.state.dsQuery;
|
||||||
const { LoadingPlaceholder } = this.props;
|
|
||||||
const { isMocking } = this.state;
|
const { isMocking } = this.state;
|
||||||
const openNodes = this.getNrOfOpenNodes();
|
const openNodes = this.getNrOfOpenNodes();
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ export class PanelModel {
|
|||||||
repeatPanelId?: number;
|
repeatPanelId?: number;
|
||||||
repeatDirection?: string;
|
repeatDirection?: string;
|
||||||
repeatedByRow?: boolean;
|
repeatedByRow?: boolean;
|
||||||
minSpan?: number;
|
maxPerRow?: number;
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
panels?: any;
|
panels?: any;
|
||||||
soloMode?: boolean;
|
soloMode?: boolean;
|
||||||
|
@ -127,7 +127,7 @@ describe('DashboardModel', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('dashboard schema version should be set to latest', () => {
|
it('dashboard schema version should be set to latest', () => {
|
||||||
expect(model.schemaVersion).toBe(16);
|
expect(model.schemaVersion).toBe(17);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('graph thresholds should be migrated', () => {
|
it('graph thresholds should be migrated', () => {
|
||||||
@ -364,14 +364,6 @@ describe('DashboardModel', () => {
|
|||||||
expect(dashboard.panels.length).toBe(2);
|
expect(dashboard.panels.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('minSpan should be twice', () => {
|
|
||||||
model.rows = [createRow({ height: 8 }, [[6]])];
|
|
||||||
model.rows[0].panels[0] = { minSpan: 12 };
|
|
||||||
|
|
||||||
const dashboard = new DashboardModel(model);
|
|
||||||
expect(dashboard.panels[0].minSpan).toBe(24);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should assign id', () => {
|
it('should assign id', () => {
|
||||||
model.rows = [createRow({ collapse: true, height: 8 }, [[6], [6]])];
|
model.rows = [createRow({ collapse: true, height: 8 }, [[6], [6]])];
|
||||||
model.rows[0].panels[0] = {};
|
model.rows[0].panels[0] = {};
|
||||||
@ -380,6 +372,16 @@ describe('DashboardModel', () => {
|
|||||||
expect(dashboard.panels[0].id).toBe(1);
|
expect(dashboard.panels[0].id).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when migrating from minSpan to maxPerRow', () => {
|
||||||
|
it('maxPerRow should be correct', () => {
|
||||||
|
const model = {
|
||||||
|
panels: [{ minSpan: 8 }],
|
||||||
|
};
|
||||||
|
const dashboard = new DashboardModel(model);
|
||||||
|
expect(dashboard.panels[0].maxPerRow).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createRow(options, panelDescriptions: any[]) {
|
function createRow(options, panelDescriptions: any[]) {
|
||||||
|
@ -143,12 +143,9 @@ export function applyPanelTimeOverrides(panel: PanelModel, timeRange: TimeRange)
|
|||||||
const timeShift = '-' + timeShiftInterpolated;
|
const timeShift = '-' + timeShiftInterpolated;
|
||||||
newTimeData.timeInfo += ' timeshift ' + timeShift;
|
newTimeData.timeInfo += ' timeshift ' + timeShift;
|
||||||
newTimeData.timeRange = {
|
newTimeData.timeRange = {
|
||||||
from: dateMath.parseDateMath(timeShift, timeRange.from, false),
|
from: dateMath.parseDateMath(timeShift, newTimeData.timeRange.from, false),
|
||||||
to: dateMath.parseDateMath(timeShift, timeRange.to, true),
|
to: dateMath.parseDateMath(timeShift, newTimeData.timeRange.to, true),
|
||||||
raw: {
|
raw: newTimeData.timeRange.raw,
|
||||||
from: timeRange.from,
|
|
||||||
to: timeRange.to,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import Remarkable from 'remarkable';
|
|||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { profiler } from 'app/core/core';
|
import { profiler } from 'app/core/core';
|
||||||
import { Emitter } from 'app/core/core';
|
import { Emitter } from 'app/core/core';
|
||||||
|
import getFactors from 'app/core/utils/factors';
|
||||||
import {
|
import {
|
||||||
duplicatePanel,
|
duplicatePanel,
|
||||||
copyPanel as copyPanelUtil,
|
copyPanel as copyPanelUtil,
|
||||||
@ -12,7 +13,7 @@ import {
|
|||||||
sharePanel as sharePanelUtil,
|
sharePanel as sharePanelUtil,
|
||||||
} from 'app/features/dashboard/utils/panel';
|
} from 'app/features/dashboard/utils/panel';
|
||||||
|
|
||||||
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, PANEL_HEADER_HEIGHT, PANEL_BORDER } from 'app/core/constants';
|
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT, PANEL_HEADER_HEIGHT, PANEL_BORDER } from 'app/core/constants';
|
||||||
|
|
||||||
export class PanelCtrl {
|
export class PanelCtrl {
|
||||||
panel: any;
|
panel: any;
|
||||||
@ -32,6 +33,7 @@ export class PanelCtrl {
|
|||||||
events: Emitter;
|
events: Emitter;
|
||||||
timing: any;
|
timing: any;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
maxPanelsPerRowOptions: number[];
|
||||||
|
|
||||||
constructor($scope, $injector) {
|
constructor($scope, $injector) {
|
||||||
this.$injector = $injector;
|
this.$injector = $injector;
|
||||||
@ -92,6 +94,7 @@ export class PanelCtrl {
|
|||||||
if (!this.editModeInitiated) {
|
if (!this.editModeInitiated) {
|
||||||
this.editModeInitiated = true;
|
this.editModeInitiated = true;
|
||||||
this.events.emit('init-edit-mode', null);
|
this.events.emit('init-edit-mode', null);
|
||||||
|
this.maxPanelsPerRowOptions = getFactors(GRID_COLUMN_COUNT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,12 +32,17 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form" ng-show="ctrl.panel.repeat && ctrl.panel.repeatDirection == 'h'">
|
<div class="gf-form" ng-show="ctrl.panel.repeat && ctrl.panel.repeatDirection == 'h'">
|
||||||
<span class="gf-form-label width-9">Min width</span>
|
<span class="gf-form-label width-9">Max per row</span>
|
||||||
<select class="gf-form-input" ng-model="ctrl.panel.minSpan" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]">
|
<select class="gf-form-input" ng-model="ctrl.panel.maxPerRow" ng-options="f for f in [2,3,4,6,12,24]">
|
||||||
<option value=""></option>
|
<option value=""></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="gf-form-hint">
|
||||||
|
<div class="gf-form-hint-text muted">
|
||||||
|
Note: You may need to change the variable selection to see this in action.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { MappingType, RangeMap, ValueMap } from '@grafana/ui';
|
import { MappingType, RangeMap, Select, ValueMap } from '@grafana/ui';
|
||||||
|
|
||||||
import { Label } from 'app/core/components/Label/Label';
|
import { Label } from 'app/core/components/Label/Label';
|
||||||
import { Select } from 'app/core/components/Select/Select';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mapping: ValueMap | RangeMap;
|
mapping: ValueMap | RangeMap;
|
||||||
|
@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import { GaugeOptions, PanelOptionsProps } from '@grafana/ui';
|
import { GaugeOptions, PanelOptionsProps } from '@grafana/ui';
|
||||||
|
|
||||||
import { Label } from 'app/core/components/Label/Label';
|
import { Label } from 'app/core/components/Label/Label';
|
||||||
import Select from 'app/core/components/Select/Select';
|
import { Select} from '@grafana/ui';
|
||||||
import UnitPicker from 'app/core/components/Select/UnitPicker';
|
import UnitPicker from 'app/core/components/Select/UnitPicker';
|
||||||
|
|
||||||
const statOptions = [
|
const statOptions = [
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rows": [],
|
"rows": [],
|
||||||
"schemaVersion": 16,
|
"schemaVersion": 17,
|
||||||
"style": "dark",
|
"style": "dark",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"templating": {
|
"templating": {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// DEPENDENCIES
|
// DEPENDENCIES
|
||||||
@import '../../node_modules/react-table/react-table.css';
|
@import '../../node_modules/react-table/react-table.css';
|
||||||
|
|
||||||
// VENDOR
|
// VENDOR
|
||||||
@ -38,9 +38,6 @@
|
|||||||
@import 'layout/lists';
|
@import 'layout/lists';
|
||||||
@import 'layout/page';
|
@import 'layout/page';
|
||||||
|
|
||||||
// LOAD @grafana/ui components
|
|
||||||
@import '../../packages/grafana-ui/src/index';
|
|
||||||
|
|
||||||
// COMPONENTS
|
// COMPONENTS
|
||||||
@import 'components/scrollbar';
|
@import 'components/scrollbar';
|
||||||
@import 'components/cards';
|
@import 'components/cards';
|
||||||
@ -97,7 +94,6 @@
|
|||||||
@import 'components/page_header';
|
@import 'components/page_header';
|
||||||
@import 'components/dashboard_settings';
|
@import 'components/dashboard_settings';
|
||||||
@import 'components/empty_list_cta';
|
@import 'components/empty_list_cta';
|
||||||
@import 'components/form_select_box';
|
|
||||||
@import 'components/panel_editor';
|
@import 'components/panel_editor';
|
||||||
@import 'components/toolbar';
|
@import 'components/toolbar';
|
||||||
@import 'components/add_data_source.scss';
|
@import 'components/add_data_source.scss';
|
||||||
@ -106,6 +102,9 @@
|
|||||||
@import 'components/value-mappings';
|
@import 'components/value-mappings';
|
||||||
@import 'components/popover-box';
|
@import 'components/popover-box';
|
||||||
|
|
||||||
|
// LOAD @grafana/ui components
|
||||||
|
@import '../../packages/grafana-ui/src/index';
|
||||||
|
|
||||||
// PAGES
|
// PAGES
|
||||||
@import 'pages/login';
|
@import 'pages/login';
|
||||||
@import 'pages/dashboard';
|
@import 'pages/dashboard';
|
||||||
|
@ -1105,7 +1105,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*", "@types/react@^16.1.0", "@types/react@^16.7.6":
|
"@types/react@*", "@types/react@16.7.6", "@types/react@^16.1.0", "@types/react@^16.7.6":
|
||||||
version "16.7.6"
|
version "16.7.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.6.tgz#80e4bab0d0731ad3ae51f320c4b08bdca5f03040"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.6.tgz#80e4bab0d0731ad3ae51f320c4b08bdca5f03040"
|
||||||
integrity sha512-QBUfzftr/8eg/q3ZRgf/GaDP6rTYc7ZNem+g4oZM38C9vXyV8AWRWaTQuW5yCoZTsfHrN7b3DeEiUnqH9SrnpA==
|
integrity sha512-QBUfzftr/8eg/q3ZRgf/GaDP6rTYc7ZNem+g4oZM38C9vXyV8AWRWaTQuW5yCoZTsfHrN7b3DeEiUnqH9SrnpA==
|
||||||
@ -3185,7 +3185,7 @@ caniuse-api@^1.5.2:
|
|||||||
lodash.memoize "^4.1.2"
|
lodash.memoize "^4.1.2"
|
||||||
lodash.uniq "^4.5.0"
|
lodash.uniq "^4.5.0"
|
||||||
|
|
||||||
caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
|
caniuse-db@1.0.30000772, caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
|
||||||
version "1.0.30000772"
|
version "1.0.30000772"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b"
|
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b"
|
||||||
integrity sha1-UarokXaChureSj2DGep21qAbUSs=
|
integrity sha1-UarokXaChureSj2DGep21qAbUSs=
|
||||||
|
Loading…
Reference in New Issue
Block a user