diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a19957b3ad..286b013a30f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Minor * **Pushover**: Adds support for images in pushover notifier [#10780](https://github.com/grafana/grafana/issues/10780), thx [@jpenalbae](https://github.com/jpenalbae) * **Cloudwatch**: Add AWS/Neptune metrics [#14231](https://github.com/grafana/grafana/issues/14231), thx [@tcpatterson](https://github.com/tcpatterson) +* **Stackdriver**: Template variables in filters using globbing format [#15182](https://github.com/grafana/grafana/issues/15182) # 6.0.0-beta1 (2019-01-30) diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index 46bab83654e..ac3dc6ebfd0 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -393,9 +393,7 @@ Analytics ID here. By default this feature is disabled. ### check_for_updates -Set to false to disable all checks to https://grafana.com for new versions of Grafana and installed plugins. Check is used -in some UI views to notify that a Grafana or plugin update exists. This option does not cause any auto updates, nor -send any sensitive information. +Set to false to disable all checks to https://grafana.com for new versions of installed plugins and to the Grafana GitHub repository to check for a newer version of Grafana. The version information is used in some UI views to notify that a new Grafana update or a plugin update exists. This option does not cause any auto updates, nor send any sensitive information. The check is run every 10 minutes.
diff --git a/packages/grafana-ui/src/types/plugin.ts b/packages/grafana-ui/src/types/plugin.ts index 00735827825..c8f156c08dc 100644 --- a/packages/grafana-ui/src/types/plugin.ts +++ b/packages/grafana-ui/src/types/plugin.ts @@ -1,6 +1,6 @@ import { ComponentClass } from 'react'; import { PanelProps, PanelOptionsProps } from './panel'; -import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint } from './datasource'; +import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint, QueryFixAction } from './datasource'; export interface DataSourceApi { /** @@ -41,6 +41,12 @@ export interface DataSourceApi { pluginExports?: PluginExports; } +export interface ExploreDataSourceApi extends DataSourceApi { + modifyQuery?(query: TQuery, action: QueryFixAction): TQuery; + getHighlighterExpression?(query: TQuery): string; + languageProvider?: any; +} + export interface QueryEditorProps { datasource: DSType; query: TQuery; @@ -48,15 +54,30 @@ export interface QueryEditorProps void; } +export interface ExploreQueryFieldProps { + datasource: DSType; + query: TQuery; + error?: string | JSX.Element; + hint?: QueryHint; + history: any[]; + onExecuteQuery?: () => void; + onQueryChange?: (value: TQuery) => void; + onExecuteHint?: (action: QueryFixAction) => void; +} + +export interface ExploreStartPageProps { + onClickExample: (query: DataQuery) => void; +} + export interface PluginExports { Datasource?: DataSourceApi; QueryCtrl?: any; - QueryEditor?: ComponentClass>; + QueryEditor?: ComponentClass>; ConfigCtrl?: any; AnnotationsQueryCtrl?: any; VariableQueryEditor?: any; - ExploreQueryField?: any; - ExploreStartPage?: any; + ExploreQueryField?: ComponentClass>; + ExploreStartPage?: ComponentClass; // Panel plugin PanelCtrl?: any; @@ -114,5 +135,3 @@ export interface PluginMetaInfo { updated: string; version: string; } - - diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index faf46118718..107f411353c 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -11,7 +11,7 @@ import { colors } from '@grafana/ui'; import TableModel, { mergeTablesIntoModel } from 'app/core/table_model'; // Types -import { RawTimeRange, IntervalValues, DataQuery } from '@grafana/ui/src/types'; +import { RawTimeRange, IntervalValues, DataQuery, DataSourceApi } from '@grafana/ui/src/types'; import TimeSeries from 'app/core/time_series2'; import { ExploreUrlState, @@ -336,3 +336,12 @@ export function clearHistory(datasourceId: string) { const historyKey = `grafana.explore.history.${datasourceId}`; store.delete(historyKey); } + +export const getQueryKeys = (queries: DataQuery[], datasourceInstance: DataSourceApi): string[] => { + const queryKeys = queries.reduce((newQueryKeys, query, index) => { + const primaryKey = datasourceInstance && datasourceInstance.name ? datasourceInstance.name : query.key; + return newQueryKeys.concat(`${primaryKey}-${index}`); + }, []); + + return queryKeys; +}; diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx new file mode 100644 index 00000000000..91da066e4cc --- /dev/null +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.test.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { AddPanelWidget, Props } from './AddPanelWidget'; +import { DashboardModel, PanelModel } from '../../state'; + +const setup = (propOverrides?: object) => { + const props: Props = { + dashboard: {} as DashboardModel, + panel: {} as PanelModel, + }; + + Object.assign(props, propOverrides); + + return shallow(); +}; + +describe('Render', () => { + it('should render component', () => { + const wrapper = setup(); + + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx index 8c1ab93cec1..135b04a8ac5 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx +++ b/public/app/features/dashboard/components/AddPanelWidget/AddPanelWidget.tsx @@ -1,12 +1,20 @@ +// Libraries import React from 'react'; import _ from 'lodash'; + +// Utils import config from 'app/core/config'; -import { PanelModel } from '../../state/PanelModel'; -import { DashboardModel } from '../../state/DashboardModel'; import store from 'app/core/store'; -import { LS_PANEL_COPY_KEY } from 'app/core/constants'; -import { updateLocation } from 'app/core/actions'; + +// Store import { store as reduxStore } from 'app/store/store'; +import { updateLocation } from 'app/core/actions'; + +// Types +import { PanelModel } from '../../state'; +import { DashboardModel } from '../../state'; +import { LS_PANEL_COPY_KEY } from 'app/core/constants'; +import { LocationUpdate } from 'app/types'; export interface Props { panel: PanelModel; @@ -46,6 +54,7 @@ export class AddPanelWidget extends React.Component { copiedPanels.push(pluginCopy); } } + return _.sortBy(copiedPanels, 'sort'); } @@ -54,28 +63,7 @@ export class AddPanelWidget extends React.Component { this.props.dashboard.removePanel(this.props.dashboard.panels[0]); } - copyButton(panel) { - return ( - - ); - } - - moveToEdit(panel) { - reduxStore.dispatch( - updateLocation({ - query: { - panelId: panel.id, - edit: true, - fullscreen: true, - }, - partial: true, - }) - ); - } - - onCreateNewPanel = () => { + onCreateNewPanel = (tab = 'queries') => { const dashboard = this.props.dashboard; const { gridPos } = this.props.panel; @@ -88,7 +76,21 @@ export class AddPanelWidget extends React.Component { dashboard.addPanel(newPanel); dashboard.removePanel(this.props.panel); - this.moveToEdit(newPanel); + const location: LocationUpdate = { + query: { + panelId: newPanel.id, + edit: true, + fullscreen: true, + }, + partial: true, + }; + + if (tab === 'visualization') { + location.query.tab = 'visualization'; + location.query.openVizPicker = true; + } + + reduxStore.dispatch(updateLocation(location)); }; onPasteCopiedPanel = panelPluginInfo => { @@ -125,30 +127,50 @@ export class AddPanelWidget extends React.Component { dashboard.removePanel(this.props.panel); }; - render() { - let addCopyButton; + renderOptionLink = (icon, text, onClick) => { + return ( + + ); + }; - if (this.state.copiedPanelPlugins.length === 1) { - addCopyButton = this.copyButton(this.state.copiedPanelPlugins[0]); - } + render() { + const { copiedPanelPlugins } = this.state; return (
+ New Panel
- - {addCopyButton} - +
+ {this.renderOptionLink('queries', 'Add Query', this.onCreateNewPanel)} + {this.renderOptionLink('visualization', 'Choose Visualization', () => + this.onCreateNewPanel('visualization') + )} +
+
+ + {copiedPanelPlugins.length === 1 && ( + + )} +
diff --git a/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss b/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss index 5a1cbee4b44..288b2e7a410 100644 --- a/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss +++ b/public/app/features/dashboard/components/AddPanelWidget/_AddPanelWidget.scss @@ -14,6 +14,9 @@ align-items: center; width: 100%; cursor: move; + background: $page-header-bg; + box-shadow: $page-header-shadow; + border-bottom: 1px solid $page-header-border-color; .gicon { font-size: 30px; @@ -26,6 +29,29 @@ } } +.add-panel-widget__title { + font-size: $font-size-md; + font-weight: $font-weight-semi-bold; + margin-right: $spacer*2; +} + +.add-panel-widget__link { + margin: 0 8px; + width: 154px; +} + +.add-panel-widget__icon { + margin-bottom: 8px; + + .gicon { + color: white; + height: 44px; + width: 53px; + position: relative; + left: 5px; + } +} + .add-panel-widget__close { margin-left: auto; background-color: transparent; @@ -34,14 +60,25 @@ margin-right: -10px; } +.add-panel-widget__create { + display: inherit; + margin-bottom: 24px; + // this is to have the big button appear centered + margin-top: 55px; +} + +.add-panel-widget__actions { + display: inherit; +} + +.add-panel-widget__action { + margin: 0 4px; +} + .add-panel-widget__btn-container { + height: 100%; display: flex; justify-content: center; align-items: center; - height: 100%; flex-direction: column; - - .btn { - margin-bottom: 10px; - } } diff --git a/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap b/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap new file mode 100644 index 00000000000..00faf48d8df --- /dev/null +++ b/public/app/features/dashboard/components/AddPanelWidget/__snapshots__/AddPanelWidget.test.tsx.snap @@ -0,0 +1,86 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Render should render component 1`] = ` +
+
+
+ + + New Panel + + +
+
+ +
+ +
+
+
+
+`; diff --git a/public/app/features/dashboard/panel_editor/PanelEditor.tsx b/public/app/features/dashboard/panel_editor/PanelEditor.tsx index d7aafb89e55..bfdc13bc8f2 100644 --- a/public/app/features/dashboard/panel_editor/PanelEditor.tsx +++ b/public/app/features/dashboard/panel_editor/PanelEditor.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; import { QueriesTab } from './QueriesTab'; -import { VisualizationTab } from './VisualizationTab'; +import VisualizationTab from './VisualizationTab'; import { GeneralTab } from './GeneralTab'; import { AlertTab } from '../../alerting/AlertTab'; @@ -38,7 +38,7 @@ export class PanelEditor extends PureComponent { onChangeTab = (tab: PanelEditorTab) => { store.dispatch( updateLocation({ - query: { tab: tab.id }, + query: { tab: tab.id, openVizPicker: null }, partial: true, }) ); diff --git a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx index 35b9b71112a..94a403c11bf 100644 --- a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx +++ b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx @@ -3,6 +3,9 @@ import React, { PureComponent } from 'react'; // Utils & Services import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; +import { connectWithStore } from 'app/core/utils/connectWithReduxStore'; +import { StoreState } from 'app/types'; +import { updateLocation } from 'app/core/actions'; // Components import { EditorTabBody, EditorToolbarView } from './EditorTabBody'; @@ -21,6 +24,8 @@ interface Props { plugin: PanelPlugin; angularPanel?: AngularComponent; onTypeChanged: (newType: PanelPlugin) => void; + updateLocation: typeof updateLocation; + urlOpenVizPicker: boolean; } interface State { @@ -38,7 +43,7 @@ export class VisualizationTab extends PureComponent { super(props); this.state = { - isVizPickerOpen: false, + isVizPickerOpen: this.props.urlOpenVizPicker, searchQuery: '', scrollTop: 0, }; @@ -149,6 +154,10 @@ export class VisualizationTab extends PureComponent { }; onCloseVizPicker = () => { + if (this.props.urlOpenVizPicker) { + this.props.updateLocation({ query: { openVizPicker: null }, partial: true }); + } + this.setState({ isVizPickerOpen: false }); }; @@ -236,3 +245,13 @@ export class VisualizationTab extends PureComponent { ); } } + +const mapStateToProps = (state: StoreState) => ({ + urlOpenVizPicker: !!state.location.query.openVizPicker +}); + +const mapDispatchToProps = { + updateLocation +}; + +export default connectWithStore(VisualizationTab, mapStateToProps, mapDispatchToProps); diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index a2890427868..b210bcccc18 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -1,5 +1,5 @@ // Libraries -import React from 'react'; +import React, { ComponentClass } from 'react'; import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; import _ from 'lodash'; @@ -18,34 +18,26 @@ import TableContainer from './TableContainer'; import TimePicker, { parseTime } from './TimePicker'; // Actions -import { - changeSize, - changeTime, - initializeExplore, - modifyQueries, - scanStart, - scanStop, - setQueries, -} from './state/actions'; +import { changeSize, changeTime, initializeExplore, modifyQueries, scanStart, setQueries } from './state/actions'; // Types -import { RawTimeRange, TimeRange, DataQuery } from '@grafana/ui'; +import { RawTimeRange, TimeRange, DataQuery, ExploreStartPageProps, ExploreDataSourceApi } from '@grafana/ui'; import { ExploreItemState, ExploreUrlState, RangeScanner, ExploreId } from 'app/types/explore'; import { StoreState } from 'app/types'; import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE, DEFAULT_UI_STATE } from 'app/core/utils/explore'; import { Emitter } from 'app/core/utils/emitter'; import { ExploreToolbar } from './ExploreToolbar'; +import { scanStopAction } from './state/actionTypes'; interface ExploreProps { - StartPage?: any; + StartPage?: ComponentClass; changeSize: typeof changeSize; changeTime: typeof changeTime; datasourceError: string; - datasourceInstance: any; + datasourceInstance: ExploreDataSourceApi; datasourceLoading: boolean | null; datasourceMissing: boolean; exploreId: ExploreId; - initialQueries: DataQuery[]; initializeExplore: typeof initializeExplore; initialized: boolean; modifyQueries: typeof modifyQueries; @@ -54,14 +46,15 @@ interface ExploreProps { scanning?: boolean; scanRange?: RawTimeRange; scanStart: typeof scanStart; - scanStop: typeof scanStop; + scanStopAction: typeof scanStopAction; setQueries: typeof setQueries; split: boolean; showingStartPage?: boolean; supportsGraph: boolean | null; supportsLogs: boolean | null; supportsTable: boolean | null; - urlState?: ExploreUrlState; + urlState: ExploreUrlState; + queryKeys: string[]; } /** @@ -173,7 +166,7 @@ export class Explore extends React.PureComponent { }; onStopScanning = () => { - this.props.scanStop(this.props.exploreId); + this.props.scanStopAction({ exploreId: this.props.exploreId }); }; render() { @@ -184,12 +177,12 @@ export class Explore extends React.PureComponent { datasourceLoading, datasourceMissing, exploreId, - initialQueries, showingStartPage, split, supportsGraph, supportsLogs, supportsTable, + queryKeys, } = this.props; const exploreClass = split ? 'explore explore-split' : 'explore'; @@ -210,7 +203,7 @@ export class Explore extends React.PureComponent { {datasourceInstance && !datasourceError && (
- + {({ width }) => (
@@ -252,13 +245,13 @@ function mapStateToProps(state: StoreState, { exploreId }) { datasourceInstance, datasourceLoading, datasourceMissing, - initialQueries, initialized, range, showingStartPage, supportsGraph, supportsLogs, supportsTable, + queryKeys, } = item; return { StartPage, @@ -266,7 +259,6 @@ function mapStateToProps(state: StoreState, { exploreId }) { datasourceInstance, datasourceLoading, datasourceMissing, - initialQueries, initialized, range, showingStartPage, @@ -274,6 +266,7 @@ function mapStateToProps(state: StoreState, { exploreId }) { supportsGraph, supportsLogs, supportsTable, + queryKeys, }; } @@ -283,7 +276,7 @@ const mapDispatchToProps = { initializeExplore, modifyQueries, scanStart, - scanStop, + scanStopAction, setQueries, }; diff --git a/public/app/features/explore/ExploreToolbar.tsx b/public/app/features/explore/ExploreToolbar.tsx index 228cfb147e8..786998c96c1 100644 --- a/public/app/features/explore/ExploreToolbar.tsx +++ b/public/app/features/explore/ExploreToolbar.tsx @@ -8,6 +8,7 @@ import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { StoreState } from 'app/types/store'; import { changeDatasource, clearQueries, splitClose, runQueries, splitOpen } from './state/actions'; import TimePicker from './TimePicker'; +import { ClickOutsideWrapper } from 'app/core/components/ClickOutsideWrapper/ClickOutsideWrapper'; enum IconSide { left = 'left', @@ -79,6 +80,10 @@ export class UnConnectedExploreToolbar extends PureComponent { this.props.runQuery(this.props.exploreId); }; + onCloseTimePicker = () => { + this.props.timepickerRef.current.setState({ isOpen: false }); + }; + render() { const { datasourceMissing, @@ -137,7 +142,9 @@ export class UnConnectedExploreToolbar extends PureComponent {
) : null}
- + + +