Skip to content

Commit 3081e0f

Browse files
committed
Merge branch 'master' into 13411-react-api-key
2 parents 362010c + abefadb commit 3081e0f

38 files changed

+1035
-106
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* **Prometheus**: Adhoc-filtering for Prometheus dashboards [#13212](https://github.com/grafana/grafana/issues/13212)
1515
* **Singlestat**: Fix gauge display accuracy for percents [#13270](https://github.com/grafana/grafana/issues/13270), thx [@tianon](https://github.com/tianon)
1616
* **Dashboard**: Prevent auto refresh from starting when loading dashboard with absolute time range [#12030](https://github.com/grafana/grafana/issues/12030)
17+
* **Templating**: New templating variable type `Text box` that allows free text input [#3173](https://github.com/grafana/grafana/issues/3173)
1718

1819
# 5.3.0 (unreleased)
1920

pkg/tsdb/cloudwatch/metric_find_query.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ func parseMultiSelectValue(input string) []string {
235235
func (e *CloudWatchExecutor) handleGetRegions(ctx context.Context, parameters *simplejson.Json, queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
236236
regions := []string{
237237
"ap-northeast-1", "ap-northeast-2", "ap-southeast-1", "ap-southeast-2", "ap-south-1", "ca-central-1", "cn-north-1", "cn-northwest-1",
238-
"eu-central-1", "eu-west-1", "eu-west-2", "eu-west-3", "sa-east-1", "us-east-1", "us-east-2", "us-gov-west-1", "us-west-1", "us-west-2",
238+
"eu-central-1", "eu-west-1", "eu-west-2", "eu-west-3", "sa-east-1", "us-east-1", "us-east-2", "us-gov-west-1", "us-west-1", "us-west-2", "us-isob-east-1", "us-iso-east-1",
239239
}
240240

241241
result := make([]suggestData, 0)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React, { SFC } from 'react';
2+
3+
export type LayoutMode = LayoutModes.Grid | LayoutModes.List;
4+
5+
export enum LayoutModes {
6+
Grid = 'grid',
7+
List = 'list',
8+
}
9+
10+
interface Props {
11+
mode: LayoutMode;
12+
onLayoutModeChanged: (mode: LayoutMode) => {};
13+
}
14+
15+
const LayoutSelector: SFC<Props> = props => {
16+
const { mode, onLayoutModeChanged } = props;
17+
return (
18+
<div className="layout-selector">
19+
<button
20+
onClick={() => {
21+
onLayoutModeChanged(LayoutModes.List);
22+
}}
23+
className={mode === LayoutModes.List ? 'active' : ''}
24+
>
25+
<i className="fa fa-list" />
26+
</button>
27+
<button
28+
onClick={() => {
29+
onLayoutModeChanged(LayoutModes.Grid);
30+
}}
31+
className={mode === LayoutModes.Grid ? 'active' : ''}
32+
>
33+
<i className="fa fa-th" />
34+
</button>
35+
</div>
36+
);
37+
};
38+
39+
export default LayoutSelector;

public/app/features/dashboard/submenu/submenu.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
<label class="gf-form-label template-variable" ng-hide="variable.hide === 1">
55
{{variable.label || variable.name}}
66
</label>
7-
<value-select-dropdown ng-if="variable.type !== 'adhoc'" variable="variable" on-updated="ctrl.variableUpdated(variable)"></value-select-dropdown>
7+
<value-select-dropdown ng-if="variable.type !== 'adhoc' && variable.type !== 'textbox'" variable="variable" on-updated="ctrl.variableUpdated(variable)"></value-select-dropdown>
8+
<input type="text" ng-if="variable.type === 'textbox'" ng-model="variable.query" class="gf-form-input width-12" ng-blur="variable.current.value != variable.query && variable.updateOptions() && ctrl.variableUpdated(variable);" ng-keydown="$event.keyCode === 13 && variable.current.value != variable.query && variable.updateOptions() && ctrl.variableUpdated(variable);" ></input>
89
</div>
910
<ad-hoc-filters ng-if="variable.type === 'adhoc'" variable="variable"></ad-hoc-filters>
1011
</div>

public/app/features/explore/Explore.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -528,10 +528,11 @@ export class Explore extends React.Component<any, ExploreState> {
528528
{!datasourceMissing ? (
529529
<div className="navbar-buttons">
530530
<Select
531-
className="datasource-picker"
532531
clearable={false}
532+
className="gf-form-input gf-form-input--form-dropdown datasource-picker"
533533
onChange={this.onChangeDatasource}
534534
options={datasources}
535+
isOpen={true}
535536
placeholder="Loading datasources..."
536537
value={selectedDatasource}
537538
/>
@@ -586,17 +587,17 @@ export class Explore extends React.Component<any, ExploreState> {
586587
/>
587588
<div className="result-options">
588589
{supportsGraph ? (
589-
<button className={`btn navbar-button ${graphButtonActive}`} onClick={this.onClickGraphButton}>
590+
<button className={`btn toggle-btn ${graphButtonActive}`} onClick={this.onClickGraphButton}>
590591
Graph
591592
</button>
592593
) : null}
593594
{supportsTable ? (
594-
<button className={`btn navbar-button ${tableButtonActive}`} onClick={this.onClickTableButton}>
595+
<button className={`btn toggle-btn ${tableButtonActive}`} onClick={this.onClickTableButton}>
595596
Table
596597
</button>
597598
) : null}
598599
{supportsLogs ? (
599-
<button className={`btn navbar-button ${logsButtonActive}`} onClick={this.onClickLogsButton}>
600+
<button className={`btn toggle-btn ${logsButtonActive}`} onClick={this.onClickLogsButton}>
600601
Logs
601602
</button>
602603
) : null}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
import { PluginActionBar, Props } from './PluginActionBar';
4+
import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
5+
6+
const setup = (propOverrides?: object) => {
7+
const props: Props = {
8+
searchQuery: '',
9+
layoutMode: LayoutModes.Grid,
10+
setLayoutMode: jest.fn(),
11+
setPluginsSearchQuery: jest.fn(),
12+
};
13+
14+
Object.assign(props, propOverrides);
15+
16+
const wrapper = shallow(<PluginActionBar {...props} />);
17+
const instance = wrapper.instance() as PluginActionBar;
18+
19+
return {
20+
wrapper,
21+
instance,
22+
};
23+
};
24+
25+
describe('Render', () => {
26+
it('should render component', () => {
27+
const { wrapper } = setup();
28+
29+
expect(wrapper).toMatchSnapshot();
30+
});
31+
});
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React, { PureComponent } from 'react';
2+
import { connect } from 'react-redux';
3+
import LayoutSelector, { LayoutMode } from '../../core/components/LayoutSelector/LayoutSelector';
4+
import { setLayoutMode, setPluginsSearchQuery } from './state/actions';
5+
import { getPluginsSearchQuery, getLayoutMode } from './state/selectors';
6+
7+
export interface Props {
8+
searchQuery: string;
9+
layoutMode: LayoutMode;
10+
setLayoutMode: typeof setLayoutMode;
11+
setPluginsSearchQuery: typeof setPluginsSearchQuery;
12+
}
13+
14+
export class PluginActionBar extends PureComponent<Props> {
15+
onSearchQueryChange = event => {
16+
this.props.setPluginsSearchQuery(event.target.value);
17+
};
18+
19+
render() {
20+
const { searchQuery, layoutMode, setLayoutMode } = this.props;
21+
22+
return (
23+
<div className="page-action-bar">
24+
<div className="gf-form gf-form--grow">
25+
<label className="gf-form--has-input-icon">
26+
<input
27+
type="text"
28+
className="gf-form-input width-20"
29+
value={searchQuery}
30+
onChange={this.onSearchQueryChange}
31+
placeholder="Filter by name or type"
32+
/>
33+
<i className="gf-form-input-icon fa fa-search" />
34+
</label>
35+
<LayoutSelector mode={layoutMode} onLayoutModeChanged={(mode: LayoutMode) => setLayoutMode(mode)} />
36+
</div>
37+
<div className="page-action-bar__spacer" />
38+
<a
39+
className="btn btn-success"
40+
href="https://grafana.com/plugins?utm_source=grafana_plugin_list"
41+
target="_blank"
42+
>
43+
Find more plugins on Grafana.com
44+
</a>
45+
</div>
46+
);
47+
}
48+
}
49+
50+
function mapStateToProps(state) {
51+
return {
52+
searchQuery: getPluginsSearchQuery(state.plugins),
53+
layoutMode: getLayoutMode(state.plugins),
54+
};
55+
}
56+
57+
const mapDispatchToProps = {
58+
setPluginsSearchQuery,
59+
setLayoutMode,
60+
};
61+
62+
export default connect(mapStateToProps, mapDispatchToProps)(PluginActionBar);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
import PluginList from './PluginList';
4+
import { getMockPlugins } from './__mocks__/pluginMocks';
5+
import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
6+
7+
const setup = (propOverrides?: object) => {
8+
const props = Object.assign(
9+
{
10+
plugins: getMockPlugins(5),
11+
layoutMode: LayoutModes.Grid,
12+
},
13+
propOverrides
14+
);
15+
16+
return shallow(<PluginList {...props} />);
17+
};
18+
19+
describe('Render', () => {
20+
it('should render component', () => {
21+
const wrapper = setup();
22+
23+
expect(wrapper).toMatchSnapshot();
24+
});
25+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React, { SFC } from 'react';
2+
import classNames from 'classnames/bind';
3+
import PluginListItem from './PluginListItem';
4+
import { Plugin } from 'app/types';
5+
import { LayoutMode, LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
6+
7+
interface Props {
8+
plugins: Plugin[];
9+
layoutMode: LayoutMode;
10+
}
11+
12+
const PluginList: SFC<Props> = props => {
13+
const { plugins, layoutMode } = props;
14+
15+
const listStyle = classNames({
16+
'card-section': true,
17+
'card-list-layout-grid': layoutMode === LayoutModes.Grid,
18+
'card-list-layout-list': layoutMode === LayoutModes.List,
19+
});
20+
21+
return (
22+
<section className={listStyle}>
23+
<ol className="card-list">
24+
{plugins.map((plugin, index) => {
25+
return <PluginListItem plugin={plugin} key={`${plugin.name}-${index}`} />;
26+
})}
27+
</ol>
28+
</section>
29+
);
30+
};
31+
32+
export default PluginList;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
import PluginListItem from './PluginListItem';
4+
import { getMockPlugin } from './__mocks__/pluginMocks';
5+
6+
const setup = (propOverrides?: object) => {
7+
const props = Object.assign(
8+
{
9+
plugin: getMockPlugin(),
10+
},
11+
propOverrides
12+
);
13+
14+
return shallow(<PluginListItem {...props} />);
15+
};
16+
17+
describe('Render', () => {
18+
it('should render component', () => {
19+
const wrapper = setup();
20+
21+
expect(wrapper).toMatchSnapshot();
22+
});
23+
24+
it('should render has plugin section', () => {
25+
const mockPlugin = getMockPlugin();
26+
mockPlugin.hasUpdate = true;
27+
const wrapper = setup({
28+
plugin: mockPlugin,
29+
});
30+
31+
expect(wrapper).toMatchSnapshot();
32+
});
33+
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React, { SFC } from 'react';
2+
import { Plugin } from 'app/types';
3+
4+
interface Props {
5+
plugin: Plugin;
6+
}
7+
8+
const PluginListItem: SFC<Props> = props => {
9+
const { plugin } = props;
10+
11+
return (
12+
<li className="card-item-wrapper">
13+
<a className="card-item" href={`plugins/${plugin.id}/edit`}>
14+
<div className="card-item-header">
15+
<div className="card-item-type">
16+
<i className={`icon-gf icon-gf-${plugin.type}`} />
17+
{plugin.type}
18+
</div>
19+
{plugin.hasUpdate && (
20+
<div className="card-item-notice">
21+
<span bs-tooltip="plugin.latestVersion">Update available!</span>
22+
</div>
23+
)}
24+
</div>
25+
<div className="card-item-body">
26+
<figure className="card-item-figure">
27+
<img src={plugin.info.logos.small} />
28+
</figure>
29+
<div className="card-item-details">
30+
<div className="card-item-name">{plugin.name}</div>
31+
<div className="card-item-sub-name">{`By ${plugin.info.author.name}`}</div>
32+
</div>
33+
</div>
34+
</a>
35+
</li>
36+
);
37+
};
38+
39+
export default PluginListItem;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
import { PluginListPage, Props } from './PluginListPage';
4+
import { NavModel, Plugin } from '../../types';
5+
import { LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector';
6+
7+
const setup = (propOverrides?: object) => {
8+
const props: Props = {
9+
navModel: {} as NavModel,
10+
plugins: [] as Plugin[],
11+
layoutMode: LayoutModes.Grid,
12+
loadPlugins: jest.fn(),
13+
};
14+
15+
Object.assign(props, propOverrides);
16+
17+
const wrapper = shallow(<PluginListPage {...props} />);
18+
const instance = wrapper.instance() as PluginListPage;
19+
20+
return {
21+
wrapper,
22+
instance,
23+
};
24+
};
25+
26+
describe('Render', () => {
27+
it('should render component', () => {
28+
const { wrapper } = setup();
29+
30+
expect(wrapper).toMatchSnapshot();
31+
});
32+
});

0 commit comments

Comments
 (0)