Merge branch 'master' into dashboard-react-page
@ -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)
|
||||
|
||||
|
@ -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.
|
||||
|
||||
<hr />
|
||||
|
||||
|
@ -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<TQuery extends DataQuery = DataQuery> {
|
||||
/**
|
||||
@ -41,6 +41,12 @@ export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
|
||||
pluginExports?: PluginExports;
|
||||
}
|
||||
|
||||
export interface ExploreDataSourceApi<TQuery extends DataQuery = DataQuery> extends DataSourceApi {
|
||||
modifyQuery?(query: TQuery, action: QueryFixAction): TQuery;
|
||||
getHighlighterExpression?(query: TQuery): string;
|
||||
languageProvider?: any;
|
||||
}
|
||||
|
||||
export interface QueryEditorProps<DSType extends DataSourceApi, TQuery extends DataQuery> {
|
||||
datasource: DSType;
|
||||
query: TQuery;
|
||||
@ -48,15 +54,30 @@ export interface QueryEditorProps<DSType extends DataSourceApi, TQuery extends D
|
||||
onChange: (value: TQuery) => void;
|
||||
}
|
||||
|
||||
export interface ExploreQueryFieldProps<DSType extends DataSourceApi, TQuery extends DataQuery> {
|
||||
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<QueryEditorProps<DataSourceApi,DataQuery>>;
|
||||
QueryEditor?: ComponentClass<QueryEditorProps<DataSourceApi, DataQuery>>;
|
||||
ConfigCtrl?: any;
|
||||
AnnotationsQueryCtrl?: any;
|
||||
VariableQueryEditor?: any;
|
||||
ExploreQueryField?: any;
|
||||
ExploreStartPage?: any;
|
||||
ExploreQueryField?: ComponentClass<ExploreQueryFieldProps<DataSourceApi, DataQuery>>;
|
||||
ExploreStartPage?: ComponentClass<ExploreStartPageProps>;
|
||||
|
||||
// Panel plugin
|
||||
PanelCtrl?: any;
|
||||
@ -114,5 +135,3 @@ export interface PluginMetaInfo {
|
||||
updated: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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(<AddPanelWidget {...props} />);
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
it('should render component', () => {
|
||||
const wrapper = setup();
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -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<Props, State> {
|
||||
copiedPanels.push(pluginCopy);
|
||||
}
|
||||
}
|
||||
|
||||
return _.sortBy(copiedPanels, 'sort');
|
||||
}
|
||||
|
||||
@ -54,28 +63,7 @@ export class AddPanelWidget extends React.Component<Props, State> {
|
||||
this.props.dashboard.removePanel(this.props.dashboard.panels[0]);
|
||||
}
|
||||
|
||||
copyButton(panel) {
|
||||
return (
|
||||
<button className="btn-inverse btn" onClick={() => this.onPasteCopiedPanel(panel)} title={panel.name}>
|
||||
Paste copied Panel
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
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<Props, State> {
|
||||
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<Props, State> {
|
||||
dashboard.removePanel(this.props.panel);
|
||||
};
|
||||
|
||||
render() {
|
||||
let addCopyButton;
|
||||
renderOptionLink = (icon, text, onClick) => {
|
||||
return (
|
||||
<div>
|
||||
<a href="#" onClick={onClick} className="add-panel-widget__link btn btn-inverse">
|
||||
<div className="add-panel-widget__icon">
|
||||
<i className={`gicon gicon-${icon}`} />
|
||||
</div>
|
||||
<span>{text}</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (this.state.copiedPanelPlugins.length === 1) {
|
||||
addCopyButton = this.copyButton(this.state.copiedPanelPlugins[0]);
|
||||
}
|
||||
render() {
|
||||
const { copiedPanelPlugins } = this.state;
|
||||
|
||||
return (
|
||||
<div className="panel-container add-panel-widget-container">
|
||||
<div className="add-panel-widget">
|
||||
<div className="add-panel-widget__header grid-drag-handle">
|
||||
<i className="gicon gicon-add-panel" />
|
||||
<span className="add-panel-widget__title">New Panel</span>
|
||||
<button className="add-panel-widget__close" onClick={this.handleCloseAddPanel}>
|
||||
<i className="fa fa-close" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="add-panel-widget__btn-container">
|
||||
<button className="btn-success btn btn-large" onClick={this.onCreateNewPanel}>
|
||||
Edit Panel
|
||||
</button>
|
||||
{addCopyButton}
|
||||
<button className="btn-inverse btn" onClick={this.onCreateNewRow}>
|
||||
Add Row
|
||||
</button>
|
||||
<div className="add-panel-widget__create">
|
||||
{this.renderOptionLink('queries', 'Add Query', this.onCreateNewPanel)}
|
||||
{this.renderOptionLink('visualization', 'Choose Visualization', () =>
|
||||
this.onCreateNewPanel('visualization')
|
||||
)}
|
||||
</div>
|
||||
<div className="add-panel-widget__actions">
|
||||
<button className="btn btn-inverse add-panel-widget__action" onClick={this.onCreateNewRow}>Convert to row</button>
|
||||
{copiedPanelPlugins.length === 1 && (
|
||||
<button
|
||||
className="btn btn-inverse add-panel-widget__action"
|
||||
onClick={() => this.onPasteCopiedPanel(copiedPanelPlugins[0])}
|
||||
>
|
||||
Paste copied panel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<div
|
||||
className="panel-container add-panel-widget-container"
|
||||
>
|
||||
<div
|
||||
className="add-panel-widget"
|
||||
>
|
||||
<div
|
||||
className="add-panel-widget__header grid-drag-handle"
|
||||
>
|
||||
<i
|
||||
className="gicon gicon-add-panel"
|
||||
/>
|
||||
<span
|
||||
className="add-panel-widget__title"
|
||||
>
|
||||
New Panel
|
||||
</span>
|
||||
<button
|
||||
className="add-panel-widget__close"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<i
|
||||
className="fa fa-close"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="add-panel-widget__btn-container"
|
||||
>
|
||||
<div
|
||||
className="add-panel-widget__create"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
className="add-panel-widget__link btn btn-inverse"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div
|
||||
className="add-panel-widget__icon"
|
||||
>
|
||||
<i
|
||||
className="gicon gicon-queries"
|
||||
/>
|
||||
</div>
|
||||
<span>
|
||||
Add Query
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
className="add-panel-widget__link btn btn-inverse"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<div
|
||||
className="add-panel-widget__icon"
|
||||
>
|
||||
<i
|
||||
className="gicon gicon-visualization"
|
||||
/>
|
||||
</div>
|
||||
<span>
|
||||
Choose Visualization
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="add-panel-widget__actions"
|
||||
>
|
||||
<button
|
||||
className="btn btn-inverse add-panel-widget__action"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Convert to row
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -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<PanelEditorProps> {
|
||||
onChangeTab = (tab: PanelEditorTab) => {
|
||||
store.dispatch(
|
||||
updateLocation({
|
||||
query: { tab: tab.id },
|
||||
query: { tab: tab.id, openVizPicker: null },
|
||||
partial: true,
|
||||
})
|
||||
);
|
||||
|
@ -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<Props, State> {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isVizPickerOpen: false,
|
||||
isVizPickerOpen: this.props.urlOpenVizPicker,
|
||||
searchQuery: '',
|
||||
scrollTop: 0,
|
||||
};
|
||||
@ -149,6 +154,10 @@ export class VisualizationTab extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
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<Props, State> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState) => ({
|
||||
urlOpenVizPicker: !!state.location.query.openVizPicker
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
updateLocation
|
||||
};
|
||||
|
||||
export default connectWithStore(VisualizationTab, mapStateToProps, mapDispatchToProps);
|
||||
|
@ -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<ExploreStartPageProps>;
|
||||
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<ExploreProps> {
|
||||
};
|
||||
|
||||
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<ExploreProps> {
|
||||
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<ExploreProps> {
|
||||
{datasourceInstance &&
|
||||
!datasourceError && (
|
||||
<div className="explore-container">
|
||||
<QueryRows exploreEvents={this.exploreEvents} exploreId={exploreId} initialQueries={initialQueries} />
|
||||
<QueryRows exploreEvents={this.exploreEvents} exploreId={exploreId} queryKeys={queryKeys} />
|
||||
<AutoSizer onResize={this.onResize} disableHeight>
|
||||
{({ width }) => (
|
||||
<main className="m-t-2" style={{ 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,
|
||||
};
|
||||
|
||||
|
@ -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<Props, {}> {
|
||||
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<Props, {}> {
|
||||
</div>
|
||||
) : null}
|
||||
<div className="explore-toolbar-content-item timepicker">
|
||||
<TimePicker ref={timepickerRef} range={range} onChangeTime={this.props.onChangeTime} />
|
||||
<ClickOutsideWrapper onClick={this.onCloseTimePicker}>
|
||||
<TimePicker ref={timepickerRef} range={range} onChangeTime={this.props.onChangeTime} />
|
||||
</ClickOutsideWrapper>
|
||||
</div>
|
||||
<div className="explore-toolbar-content-item">
|
||||
<button className="btn navbar-button navbar-button--no-icon" onClick={this.onClearAll}>
|
||||
|
@ -14,7 +14,7 @@ interface QueryEditorProps {
|
||||
datasource: any;
|
||||
error?: string | JSX.Element;
|
||||
onExecuteQuery?: () => void;
|
||||
onQueryChange?: (value: DataQuery, override?: boolean) => void;
|
||||
onQueryChange?: (value: DataQuery) => void;
|
||||
initialQuery: DataQuery;
|
||||
exploreEvents: Emitter;
|
||||
range: RawTimeRange;
|
||||
@ -40,20 +40,17 @@ export default class QueryEditor extends PureComponent<QueryEditorProps, any> {
|
||||
datasource,
|
||||
target,
|
||||
refresh: () => {
|
||||
this.props.onQueryChange(target, false);
|
||||
this.props.onQueryChange(target);
|
||||
this.props.onExecuteQuery();
|
||||
},
|
||||
events: exploreEvents,
|
||||
panel: {
|
||||
datasource,
|
||||
targets: [target],
|
||||
},
|
||||
panel: { datasource, targets: [target] },
|
||||
dashboard: {},
|
||||
},
|
||||
};
|
||||
|
||||
this.component = loader.load(this.element, scopeProps, template);
|
||||
this.props.onQueryChange(target, false);
|
||||
this.props.onQueryChange(target);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -33,10 +33,9 @@ export interface QueryFieldProps {
|
||||
cleanText?: (text: string) => string;
|
||||
disabled?: boolean;
|
||||
initialQuery: string | null;
|
||||
onBlur?: () => void;
|
||||
onFocus?: () => void;
|
||||
onExecuteQuery?: () => void;
|
||||
onQueryChange?: (value: string) => void;
|
||||
onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput;
|
||||
onValueChanged?: (value: string) => void;
|
||||
onWillApplySuggestion?: (suggestion: string, state: QueryFieldState) => string;
|
||||
placeholder?: string;
|
||||
portalOrigin?: string;
|
||||
@ -51,6 +50,7 @@ export interface QueryFieldState {
|
||||
typeaheadPrefix: string;
|
||||
typeaheadText: string;
|
||||
value: Value;
|
||||
lastExecutedValue: Value;
|
||||
}
|
||||
|
||||
export interface TypeaheadInput {
|
||||
@ -90,6 +90,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
typeaheadPrefix: '',
|
||||
typeaheadText: '',
|
||||
value: makeValue(this.placeholdersBuffer.toString(), props.syntax),
|
||||
lastExecutedValue: null,
|
||||
};
|
||||
}
|
||||
|
||||
@ -132,11 +133,11 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
if (this.placeholdersBuffer.hasPlaceholders()) {
|
||||
change.move(this.placeholdersBuffer.getNextMoveOffset()).focus();
|
||||
}
|
||||
this.onChange(change);
|
||||
this.onChange(change, true);
|
||||
}
|
||||
}
|
||||
|
||||
onChange = ({ value }) => {
|
||||
onChange = ({ value }, invokeParentOnValueChanged?: boolean) => {
|
||||
const documentChanged = value.document !== this.state.value.document;
|
||||
const prevValue = this.state.value;
|
||||
|
||||
@ -144,8 +145,8 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
this.setState({ value }, () => {
|
||||
if (documentChanged) {
|
||||
const textChanged = Plain.serialize(prevValue) !== Plain.serialize(value);
|
||||
if (textChanged) {
|
||||
this.handleChangeValue();
|
||||
if (textChanged && invokeParentOnValueChanged) {
|
||||
this.executeOnQueryChangeAndExecuteQueries();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -159,11 +160,16 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
}
|
||||
};
|
||||
|
||||
handleChangeValue = () => {
|
||||
executeOnQueryChangeAndExecuteQueries = () => {
|
||||
// Send text change to parent
|
||||
const { onValueChanged } = this.props;
|
||||
if (onValueChanged) {
|
||||
onValueChanged(Plain.serialize(this.state.value));
|
||||
const { onQueryChange, onExecuteQuery } = this.props;
|
||||
if (onQueryChange) {
|
||||
onQueryChange(Plain.serialize(this.state.value));
|
||||
}
|
||||
|
||||
if (onExecuteQuery) {
|
||||
onExecuteQuery();
|
||||
this.setState({ lastExecutedValue: this.state.value });
|
||||
}
|
||||
};
|
||||
|
||||
@ -288,8 +294,37 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
.focus();
|
||||
}
|
||||
|
||||
onKeyDown = (event, change) => {
|
||||
handleEnterAndTabKey = change => {
|
||||
const { typeaheadIndex, suggestions } = this.state;
|
||||
if (this.menuEl) {
|
||||
// Dont blur input
|
||||
event.preventDefault();
|
||||
if (!suggestions || suggestions.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const suggestion = getSuggestionByIndex(suggestions, typeaheadIndex);
|
||||
const nextChange = this.applyTypeahead(change, suggestion);
|
||||
|
||||
const insertTextOperation = nextChange.operations.find(operation => operation.type === 'insert_text');
|
||||
if (insertTextOperation) {
|
||||
const suggestionText = insertTextOperation.text;
|
||||
this.placeholdersBuffer.setNextPlaceholderValue(suggestionText);
|
||||
if (this.placeholdersBuffer.hasPlaceholders()) {
|
||||
nextChange.move(this.placeholdersBuffer.getNextMoveOffset()).focus();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
this.executeOnQueryChangeAndExecuteQueries();
|
||||
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
onKeyDown = (event, change) => {
|
||||
const { typeaheadIndex } = this.state;
|
||||
|
||||
switch (event.key) {
|
||||
case 'Escape': {
|
||||
@ -312,27 +347,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
}
|
||||
case 'Enter':
|
||||
case 'Tab': {
|
||||
if (this.menuEl) {
|
||||
// Dont blur input
|
||||
event.preventDefault();
|
||||
if (!suggestions || suggestions.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const suggestion = getSuggestionByIndex(suggestions, typeaheadIndex);
|
||||
const nextChange = this.applyTypeahead(change, suggestion);
|
||||
|
||||
const insertTextOperation = nextChange.operations.find(operation => operation.type === 'insert_text');
|
||||
if (insertTextOperation) {
|
||||
const suggestionText = insertTextOperation.text;
|
||||
this.placeholdersBuffer.setNextPlaceholderValue(suggestionText);
|
||||
if (this.placeholdersBuffer.hasPlaceholders()) {
|
||||
nextChange.move(this.placeholdersBuffer.getNextMoveOffset()).focus();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return this.handleEnterAndTabKey(change);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -364,39 +379,33 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
|
||||
resetTypeahead = () => {
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
suggestions: [],
|
||||
typeaheadIndex: 0,
|
||||
typeaheadPrefix: '',
|
||||
typeaheadContext: null,
|
||||
});
|
||||
this.setState({ suggestions: [], typeaheadIndex: 0, typeaheadPrefix: '', typeaheadContext: null });
|
||||
this.resetTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
handleBlur = () => {
|
||||
const { onBlur } = this.props;
|
||||
handleBlur = (event, change) => {
|
||||
const { lastExecutedValue } = this.state;
|
||||
const previousValue = lastExecutedValue ? Plain.serialize(this.state.lastExecutedValue) : null;
|
||||
const currentValue = Plain.serialize(change.value);
|
||||
|
||||
// If we dont wait here, menu clicks wont work because the menu
|
||||
// will be gone.
|
||||
this.resetTimer = setTimeout(this.resetTypeahead, 100);
|
||||
// Disrupting placeholder entry wipes all remaining placeholders needing input
|
||||
this.placeholdersBuffer.clearPlaceholders();
|
||||
if (onBlur) {
|
||||
onBlur();
|
||||
|
||||
if (previousValue !== currentValue) {
|
||||
this.executeOnQueryChangeAndExecuteQueries();
|
||||
}
|
||||
};
|
||||
|
||||
handleFocus = () => {
|
||||
const { onFocus } = this.props;
|
||||
if (onFocus) {
|
||||
onFocus();
|
||||
}
|
||||
};
|
||||
handleFocus = () => {};
|
||||
|
||||
onClickMenu = (item: CompletionItem) => {
|
||||
// Manually triggering change
|
||||
const change = this.applyTypeahead(this.state.value.change(), item);
|
||||
this.onChange(change);
|
||||
this.onChange(change, true);
|
||||
};
|
||||
|
||||
updateMenu = () => {
|
||||
@ -459,6 +468,14 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
);
|
||||
};
|
||||
|
||||
handlePaste = (event: ClipboardEvent, change: Editor) => {
|
||||
const pastedValue = event.clipboardData.getData('Text');
|
||||
const newValue = change.value.change().insertText(pastedValue);
|
||||
this.onChange(newValue);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { disabled } = this.props;
|
||||
const wrapperClassName = classnames('slate-query-field__wrapper', {
|
||||
@ -475,6 +492,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
onKeyDown={this.onKeyDown}
|
||||
onChange={this.onChange}
|
||||
onFocus={this.handleFocus}
|
||||
onPaste={this.handlePaste}
|
||||
placeholder={this.props.placeholder}
|
||||
plugins={this.plugins}
|
||||
spellCheck={false}
|
||||
|
@ -9,20 +9,14 @@ import QueryEditor from './QueryEditor';
|
||||
import QueryTransactionStatus from './QueryTransactionStatus';
|
||||
|
||||
// Actions
|
||||
import {
|
||||
addQueryRow,
|
||||
changeQuery,
|
||||
highlightLogsExpression,
|
||||
modifyQueries,
|
||||
removeQueryRow,
|
||||
runQueries,
|
||||
} from './state/actions';
|
||||
import { changeQuery, modifyQueries, runQueries, addQueryRow } from './state/actions';
|
||||
|
||||
// Types
|
||||
import { StoreState } from 'app/types';
|
||||
import { RawTimeRange, DataQuery, QueryHint } from '@grafana/ui';
|
||||
import { RawTimeRange, DataQuery, ExploreDataSourceApi, QueryHint, QueryFixAction } from '@grafana/ui';
|
||||
import { QueryTransaction, HistoryItem, ExploreItemState, ExploreId } from 'app/types/explore';
|
||||
import { Emitter } from 'app/core/utils/emitter';
|
||||
import { highlightLogsExpressionAction, removeQueryRowAction } from './state/actionTypes';
|
||||
|
||||
function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint {
|
||||
const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0);
|
||||
@ -37,16 +31,16 @@ interface QueryRowProps {
|
||||
changeQuery: typeof changeQuery;
|
||||
className?: string;
|
||||
exploreId: ExploreId;
|
||||
datasourceInstance: any;
|
||||
highlightLogsExpression: typeof highlightLogsExpression;
|
||||
datasourceInstance: ExploreDataSourceApi;
|
||||
highlightLogsExpressionAction: typeof highlightLogsExpressionAction;
|
||||
history: HistoryItem[];
|
||||
index: number;
|
||||
initialQuery: DataQuery;
|
||||
query: DataQuery;
|
||||
modifyQueries: typeof modifyQueries;
|
||||
queryTransactions: QueryTransaction[];
|
||||
exploreEvents: Emitter;
|
||||
range: RawTimeRange;
|
||||
removeQueryRow: typeof removeQueryRow;
|
||||
removeQueryRowAction: typeof removeQueryRowAction;
|
||||
runQueries: typeof runQueries;
|
||||
}
|
||||
|
||||
@ -78,29 +72,30 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
||||
this.onChangeQuery(null, true);
|
||||
};
|
||||
|
||||
onClickHintFix = action => {
|
||||
onClickHintFix = (action: QueryFixAction) => {
|
||||
const { datasourceInstance, exploreId, index } = this.props;
|
||||
if (datasourceInstance && datasourceInstance.modifyQuery) {
|
||||
const modifier = (queries: DataQuery, action: any) => datasourceInstance.modifyQuery(queries, action);
|
||||
const modifier = (queries: DataQuery, action: QueryFixAction) => datasourceInstance.modifyQuery(queries, action);
|
||||
this.props.modifyQueries(exploreId, action, index, modifier);
|
||||
}
|
||||
};
|
||||
|
||||
onClickRemoveButton = () => {
|
||||
const { exploreId, index } = this.props;
|
||||
this.props.removeQueryRow(exploreId, index);
|
||||
this.props.removeQueryRowAction({ exploreId, index });
|
||||
};
|
||||
|
||||
updateLogsHighlights = _.debounce((value: DataQuery) => {
|
||||
const { datasourceInstance } = this.props;
|
||||
if (datasourceInstance.getHighlighterExpression) {
|
||||
const { exploreId } = this.props;
|
||||
const expressions = [datasourceInstance.getHighlighterExpression(value)];
|
||||
this.props.highlightLogsExpression(this.props.exploreId, expressions);
|
||||
this.props.highlightLogsExpressionAction({ exploreId, expressions });
|
||||
}
|
||||
}, 500);
|
||||
|
||||
render() {
|
||||
const { datasourceInstance, history, index, initialQuery, queryTransactions, exploreEvents, range } = this.props;
|
||||
const { datasourceInstance, history, index, query, queryTransactions, exploreEvents, range } = this.props;
|
||||
const transactions = queryTransactions.filter(t => t.rowIndex === index);
|
||||
const transactionWithError = transactions.find(t => t.error !== undefined);
|
||||
const hint = getFirstHintFromTransactions(transactions);
|
||||
@ -115,12 +110,12 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
||||
{QueryField ? (
|
||||
<QueryField
|
||||
datasource={datasourceInstance}
|
||||
query={query}
|
||||
error={queryError}
|
||||
hint={hint}
|
||||
initialQuery={initialQuery}
|
||||
history={history}
|
||||
onClickHintFix={this.onClickHintFix}
|
||||
onPressEnter={this.onExecuteQuery}
|
||||
onExecuteQuery={this.onExecuteQuery}
|
||||
onExecuteHint={this.onClickHintFix}
|
||||
onQueryChange={this.onChangeQuery}
|
||||
/>
|
||||
) : (
|
||||
@ -129,7 +124,7 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
||||
error={queryError}
|
||||
onQueryChange={this.onChangeQuery}
|
||||
onExecuteQuery={this.onExecuteQuery}
|
||||
initialQuery={initialQuery}
|
||||
initialQuery={query}
|
||||
exploreEvents={exploreEvents}
|
||||
range={range}
|
||||
/>
|
||||
@ -160,17 +155,17 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
||||
function mapStateToProps(state: StoreState, { exploreId, index }) {
|
||||
const explore = state.explore;
|
||||
const item: ExploreItemState = explore[exploreId];
|
||||
const { datasourceInstance, history, initialQueries, queryTransactions, range } = item;
|
||||
const initialQuery = initialQueries[index];
|
||||
return { datasourceInstance, history, initialQuery, queryTransactions, range };
|
||||
const { datasourceInstance, history, queries, queryTransactions, range } = item;
|
||||
const query = queries[index];
|
||||
return { datasourceInstance, history, query, queryTransactions, range };
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
addQueryRow,
|
||||
changeQuery,
|
||||
highlightLogsExpression,
|
||||
highlightLogsExpressionAction,
|
||||
modifyQueries,
|
||||
removeQueryRow,
|
||||
removeQueryRowAction,
|
||||
runQueries,
|
||||
};
|
||||
|
||||
|
@ -6,25 +6,23 @@ import QueryRow from './QueryRow';
|
||||
|
||||
// Types
|
||||
import { Emitter } from 'app/core/utils/emitter';
|
||||
import { DataQuery } from '@grafana/ui/src/types';
|
||||
import { ExploreId } from 'app/types/explore';
|
||||
|
||||
interface QueryRowsProps {
|
||||
className?: string;
|
||||
exploreEvents: Emitter;
|
||||
exploreId: ExploreId;
|
||||
initialQueries: DataQuery[];
|
||||
queryKeys: string[];
|
||||
}
|
||||
|
||||
export default class QueryRows extends PureComponent<QueryRowsProps> {
|
||||
render() {
|
||||
const { className = '', exploreEvents, exploreId, initialQueries } = this.props;
|
||||
const { className = '', exploreEvents, exploreId, queryKeys } = this.props;
|
||||
return (
|
||||
<div className={className}>
|
||||
{initialQueries.map((query, index) => (
|
||||
// TODO instead of relying on initialQueries, move to react key list in redux
|
||||
<QueryRow key={query.key} exploreEvents={exploreEvents} exploreId={exploreId} index={index} />
|
||||
))}
|
||||
{queryKeys.map((key, index) => {
|
||||
return <QueryRow key={key} exploreEvents={exploreEvents} exploreId={exploreId} index={index} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -7,16 +7,16 @@ import { StoreState } from 'app/types';
|
||||
import { ExploreId, ExploreUrlState } from 'app/types/explore';
|
||||
import { parseUrlState } from 'app/core/utils/explore';
|
||||
|
||||
import { initializeExploreSplit, resetExplore } from './state/actions';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
import Explore from './Explore';
|
||||
import { CustomScrollbar } from '@grafana/ui';
|
||||
import { initializeExploreSplitAction, resetExploreAction } from './state/actionTypes';
|
||||
|
||||
interface WrapperProps {
|
||||
initializeExploreSplit: typeof initializeExploreSplit;
|
||||
initializeExploreSplitAction: typeof initializeExploreSplitAction;
|
||||
split: boolean;
|
||||
updateLocation: typeof updateLocation;
|
||||
resetExplore: typeof resetExplore;
|
||||
resetExploreAction: typeof resetExploreAction;
|
||||
urlStates: { [key: string]: string };
|
||||
}
|
||||
|
||||
@ -39,12 +39,12 @@ export class Wrapper extends Component<WrapperProps> {
|
||||
|
||||
componentDidMount() {
|
||||
if (this.initialSplit) {
|
||||
this.props.initializeExploreSplit();
|
||||
this.props.initializeExploreSplitAction();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.resetExplore();
|
||||
this.props.resetExploreAction();
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -77,9 +77,9 @@ const mapStateToProps = (state: StoreState) => {
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
initializeExploreSplit,
|
||||
initializeExploreSplitAction,
|
||||
updateLocation,
|
||||
resetExplore,
|
||||
resetExploreAction,
|
||||
};
|
||||
|
||||
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(Wrapper));
|
||||
|
@ -1,6 +1,13 @@
|
||||
// Types
|
||||
import { Emitter } from 'app/core/core';
|
||||
import { RawTimeRange, TimeRange, DataQuery, DataSourceSelectItem, DataSourceApi } from '@grafana/ui/src/types';
|
||||
import {
|
||||
RawTimeRange,
|
||||
TimeRange,
|
||||
DataQuery,
|
||||
DataSourceSelectItem,
|
||||
DataSourceApi,
|
||||
QueryFixAction,
|
||||
} from '@grafana/ui/src/types';
|
||||
import {
|
||||
ExploreId,
|
||||
ExploreItemState,
|
||||
@ -10,234 +17,26 @@ import {
|
||||
QueryTransaction,
|
||||
ExploreUIState,
|
||||
} from 'app/types/explore';
|
||||
import { actionCreatorFactory, noPayloadActionCreatorFactory, ActionOf } from 'app/core/redux/actionCreatorFactory';
|
||||
|
||||
/** Higher order actions
|
||||
*
|
||||
*/
|
||||
export enum ActionTypes {
|
||||
AddQueryRow = 'explore/ADD_QUERY_ROW',
|
||||
ChangeDatasource = 'explore/CHANGE_DATASOURCE',
|
||||
ChangeQuery = 'explore/CHANGE_QUERY',
|
||||
ChangeSize = 'explore/CHANGE_SIZE',
|
||||
ChangeTime = 'explore/CHANGE_TIME',
|
||||
ClearQueries = 'explore/CLEAR_QUERIES',
|
||||
HighlightLogsExpression = 'explore/HIGHLIGHT_LOGS_EXPRESSION',
|
||||
InitializeExplore = 'explore/INITIALIZE_EXPLORE',
|
||||
InitializeExploreSplit = 'explore/INITIALIZE_EXPLORE_SPLIT',
|
||||
LoadDatasourceFailure = 'explore/LOAD_DATASOURCE_FAILURE',
|
||||
LoadDatasourceMissing = 'explore/LOAD_DATASOURCE_MISSING',
|
||||
LoadDatasourcePending = 'explore/LOAD_DATASOURCE_PENDING',
|
||||
LoadDatasourceSuccess = 'explore/LOAD_DATASOURCE_SUCCESS',
|
||||
ModifyQueries = 'explore/MODIFY_QUERIES',
|
||||
QueryTransactionFailure = 'explore/QUERY_TRANSACTION_FAILURE',
|
||||
QueryTransactionStart = 'explore/QUERY_TRANSACTION_START',
|
||||
QueryTransactionSuccess = 'explore/QUERY_TRANSACTION_SUCCESS',
|
||||
RemoveQueryRow = 'explore/REMOVE_QUERY_ROW',
|
||||
RunQueries = 'explore/RUN_QUERIES',
|
||||
RunQueriesEmpty = 'explore/RUN_QUERIES_EMPTY',
|
||||
ScanRange = 'explore/SCAN_RANGE',
|
||||
ScanStart = 'explore/SCAN_START',
|
||||
ScanStop = 'explore/SCAN_STOP',
|
||||
SetQueries = 'explore/SET_QUERIES',
|
||||
SplitClose = 'explore/SPLIT_CLOSE',
|
||||
SplitOpen = 'explore/SPLIT_OPEN',
|
||||
StateSave = 'explore/STATE_SAVE',
|
||||
ToggleGraph = 'explore/TOGGLE_GRAPH',
|
||||
ToggleLogs = 'explore/TOGGLE_LOGS',
|
||||
ToggleTable = 'explore/TOGGLE_TABLE',
|
||||
UpdateDatasourceInstance = 'explore/UPDATE_DATASOURCE_INSTANCE',
|
||||
ResetExplore = 'explore/RESET_EXPLORE',
|
||||
QueriesImported = 'explore/QueriesImported',
|
||||
}
|
||||
|
||||
export interface AddQueryRowAction {
|
||||
type: ActionTypes.AddQueryRow;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
index: number;
|
||||
query: DataQuery;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ChangeQueryAction {
|
||||
type: ActionTypes.ChangeQuery;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
query: DataQuery;
|
||||
index: number;
|
||||
override: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ChangeSizeAction {
|
||||
type: ActionTypes.ChangeSize;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ChangeTimeAction {
|
||||
type: ActionTypes.ChangeTime;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
range: TimeRange;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ClearQueriesAction {
|
||||
type: ActionTypes.ClearQueries;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
};
|
||||
}
|
||||
|
||||
export interface HighlightLogsExpressionAction {
|
||||
type: ActionTypes.HighlightLogsExpression;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
expressions: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface InitializeExploreAction {
|
||||
type: ActionTypes.InitializeExplore;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
containerWidth: number;
|
||||
eventBridge: Emitter;
|
||||
exploreDatasources: DataSourceSelectItem[];
|
||||
queries: DataQuery[];
|
||||
range: RawTimeRange;
|
||||
ui: ExploreUIState;
|
||||
};
|
||||
}
|
||||
|
||||
export interface InitializeExploreSplitAction {
|
||||
type: ActionTypes.InitializeExploreSplit;
|
||||
}
|
||||
|
||||
export interface LoadDatasourceFailureAction {
|
||||
type: ActionTypes.LoadDatasourceFailure;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
error: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LoadDatasourcePendingAction {
|
||||
type: ActionTypes.LoadDatasourcePending;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
requestedDatasourceName: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LoadDatasourceMissingAction {
|
||||
type: ActionTypes.LoadDatasourceMissing;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LoadDatasourceSuccessAction {
|
||||
type: ActionTypes.LoadDatasourceSuccess;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
StartPage?: any;
|
||||
datasourceInstance: any;
|
||||
history: HistoryItem[];
|
||||
logsHighlighterExpressions?: any[];
|
||||
showingStartPage: boolean;
|
||||
supportsGraph: boolean;
|
||||
supportsLogs: boolean;
|
||||
supportsTable: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ModifyQueriesAction {
|
||||
type: ActionTypes.ModifyQueries;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
modification: any;
|
||||
index: number;
|
||||
modifier: (queries: DataQuery[], modification: any) => DataQuery[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface QueryTransactionFailureAction {
|
||||
type: ActionTypes.QueryTransactionFailure;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
queryTransactions: QueryTransaction[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface QueryTransactionStartAction {
|
||||
type: ActionTypes.QueryTransactionStart;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
resultType: ResultType;
|
||||
rowIndex: number;
|
||||
transaction: QueryTransaction;
|
||||
};
|
||||
}
|
||||
|
||||
export interface QueryTransactionSuccessAction {
|
||||
type: ActionTypes.QueryTransactionSuccess;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
history: HistoryItem[];
|
||||
queryTransactions: QueryTransaction[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface RemoveQueryRowAction {
|
||||
type: ActionTypes.RemoveQueryRow;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
index: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RunQueriesEmptyAction {
|
||||
type: ActionTypes.RunQueriesEmpty;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ScanStartAction {
|
||||
type: ActionTypes.ScanStart;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
scanner: RangeScanner;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ScanRangeAction {
|
||||
type: ActionTypes.ScanRange;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
range: RawTimeRange;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ScanStopAction {
|
||||
type: ActionTypes.ScanStop;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SetQueriesAction {
|
||||
type: ActionTypes.SetQueries;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
queries: DataQuery[];
|
||||
};
|
||||
payload: {};
|
||||
}
|
||||
|
||||
export interface SplitCloseAction {
|
||||
type: ActionTypes.SplitClose;
|
||||
payload: {};
|
||||
}
|
||||
|
||||
export interface SplitOpenAction {
|
||||
@ -247,80 +46,385 @@ export interface SplitOpenAction {
|
||||
};
|
||||
}
|
||||
|
||||
export interface StateSaveAction {
|
||||
type: ActionTypes.StateSave;
|
||||
}
|
||||
|
||||
export interface ToggleTableAction {
|
||||
type: ActionTypes.ToggleTable;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ToggleGraphAction {
|
||||
type: ActionTypes.ToggleGraph;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ToggleLogsAction {
|
||||
type: ActionTypes.ToggleLogs;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateDatasourceInstanceAction {
|
||||
type: ActionTypes.UpdateDatasourceInstance;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
datasourceInstance: DataSourceApi;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ResetExploreAction {
|
||||
type: ActionTypes.ResetExplore;
|
||||
payload: {};
|
||||
}
|
||||
|
||||
export interface QueriesImported {
|
||||
type: ActionTypes.QueriesImported;
|
||||
payload: {
|
||||
exploreId: ExploreId;
|
||||
queries: DataQuery[];
|
||||
};
|
||||
/** Lower order actions
|
||||
*
|
||||
*/
|
||||
export interface AddQueryRowPayload {
|
||||
exploreId: ExploreId;
|
||||
index: number;
|
||||
query: DataQuery;
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| AddQueryRowAction
|
||||
| ChangeQueryAction
|
||||
| ChangeSizeAction
|
||||
| ChangeTimeAction
|
||||
| ClearQueriesAction
|
||||
| HighlightLogsExpressionAction
|
||||
| InitializeExploreAction
|
||||
export interface ChangeQueryPayload {
|
||||
exploreId: ExploreId;
|
||||
query: DataQuery;
|
||||
index: number;
|
||||
override: boolean;
|
||||
}
|
||||
|
||||
export interface ChangeSizePayload {
|
||||
exploreId: ExploreId;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface ChangeTimePayload {
|
||||
exploreId: ExploreId;
|
||||
range: TimeRange;
|
||||
}
|
||||
|
||||
export interface ClearQueriesPayload {
|
||||
exploreId: ExploreId;
|
||||
}
|
||||
|
||||
export interface HighlightLogsExpressionPayload {
|
||||
exploreId: ExploreId;
|
||||
expressions: string[];
|
||||
}
|
||||
|
||||
export interface InitializeExplorePayload {
|
||||
exploreId: ExploreId;
|
||||
containerWidth: number;
|
||||
eventBridge: Emitter;
|
||||
exploreDatasources: DataSourceSelectItem[];
|
||||
queries: DataQuery[];
|
||||
range: RawTimeRange;
|
||||
ui: ExploreUIState;
|
||||
}
|
||||
|
||||
export interface LoadDatasourceFailurePayload {
|
||||
exploreId: ExploreId;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface LoadDatasourceMissingPayload {
|
||||
exploreId: ExploreId;
|
||||
}
|
||||
|
||||
export interface LoadDatasourcePendingPayload {
|
||||
exploreId: ExploreId;
|
||||
requestedDatasourceName: string;
|
||||
}
|
||||
|
||||
export interface LoadDatasourceSuccessPayload {
|
||||
exploreId: ExploreId;
|
||||
StartPage?: any;
|
||||
datasourceInstance: any;
|
||||
history: HistoryItem[];
|
||||
logsHighlighterExpressions?: any[];
|
||||
showingStartPage: boolean;
|
||||
supportsGraph: boolean;
|
||||
supportsLogs: boolean;
|
||||
supportsTable: boolean;
|
||||
}
|
||||
|
||||
export interface ModifyQueriesPayload {
|
||||
exploreId: ExploreId;
|
||||
modification: QueryFixAction;
|
||||
index: number;
|
||||
modifier: (query: DataQuery, modification: QueryFixAction) => DataQuery;
|
||||
}
|
||||
|
||||
export interface QueryTransactionFailurePayload {
|
||||
exploreId: ExploreId;
|
||||
queryTransactions: QueryTransaction[];
|
||||
}
|
||||
|
||||
export interface QueryTransactionStartPayload {
|
||||
exploreId: ExploreId;
|
||||
resultType: ResultType;
|
||||
rowIndex: number;
|
||||
transaction: QueryTransaction;
|
||||
}
|
||||
|
||||
export interface QueryTransactionSuccessPayload {
|
||||
exploreId: ExploreId;
|
||||
history: HistoryItem[];
|
||||
queryTransactions: QueryTransaction[];
|
||||
}
|
||||
|
||||
export interface RemoveQueryRowPayload {
|
||||
exploreId: ExploreId;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface RunQueriesEmptyPayload {
|
||||
exploreId: ExploreId;
|
||||
}
|
||||
|
||||
export interface ScanStartPayload {
|
||||
exploreId: ExploreId;
|
||||
scanner: RangeScanner;
|
||||
}
|
||||
|
||||
export interface ScanRangePayload {
|
||||
exploreId: ExploreId;
|
||||
range: RawTimeRange;
|
||||
}
|
||||
|
||||
export interface ScanStopPayload {
|
||||
exploreId: ExploreId;
|
||||
}
|
||||
|
||||
export interface SetQueriesPayload {
|
||||
exploreId: ExploreId;
|
||||
queries: DataQuery[];
|
||||
}
|
||||
|
||||
export interface SplitOpenPayload {
|
||||
itemState: ExploreItemState;
|
||||
}
|
||||
|
||||
export interface ToggleTablePayload {
|
||||
exploreId: ExploreId;
|
||||
}
|
||||
|
||||
export interface ToggleGraphPayload {
|
||||
exploreId: ExploreId;
|
||||
}
|
||||
|
||||
export interface ToggleLogsPayload {
|
||||
exploreId: ExploreId;
|
||||
}
|
||||
|
||||
export interface UpdateDatasourceInstancePayload {
|
||||
exploreId: ExploreId;
|
||||
datasourceInstance: DataSourceApi;
|
||||
}
|
||||
|
||||
export interface QueriesImportedPayload {
|
||||
exploreId: ExploreId;
|
||||
queries: DataQuery[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a query row after the row with the given index.
|
||||
*/
|
||||
export const addQueryRowAction = actionCreatorFactory<AddQueryRowPayload>('explore/ADD_QUERY_ROW').create();
|
||||
|
||||
/**
|
||||
* Loads a new datasource identified by the given name.
|
||||
*/
|
||||
export const changeDatasourceAction = noPayloadActionCreatorFactory('explore/CHANGE_DATASOURCE').create();
|
||||
|
||||
/**
|
||||
* Query change handler for the query row with the given index.
|
||||
* If `override` is reset the query modifications and run the queries. Use this to set queries via a link.
|
||||
*/
|
||||
export const changeQueryAction = actionCreatorFactory<ChangeQueryPayload>('explore/CHANGE_QUERY').create();
|
||||
|
||||
/**
|
||||
* Keep track of the Explore container size, in particular the width.
|
||||
* The width will be used to calculate graph intervals (number of datapoints).
|
||||
*/
|
||||
export const changeSizeAction = actionCreatorFactory<ChangeSizePayload>('explore/CHANGE_SIZE').create();
|
||||
|
||||
/**
|
||||
* Change the time range of Explore. Usually called from the Timepicker or a graph interaction.
|
||||
*/
|
||||
export const changeTimeAction = actionCreatorFactory<ChangeTimePayload>('explore/CHANGE_TIME').create();
|
||||
|
||||
/**
|
||||
* Clear all queries and results.
|
||||
*/
|
||||
export const clearQueriesAction = actionCreatorFactory<ClearQueriesPayload>('explore/CLEAR_QUERIES').create();
|
||||
|
||||
/**
|
||||
* Highlight expressions in the log results
|
||||
*/
|
||||
export const highlightLogsExpressionAction = actionCreatorFactory<HighlightLogsExpressionPayload>(
|
||||
'explore/HIGHLIGHT_LOGS_EXPRESSION'
|
||||
).create();
|
||||
|
||||
/**
|
||||
* Initialize Explore state with state from the URL and the React component.
|
||||
* Call this only on components for with the Explore state has not been initialized.
|
||||
*/
|
||||
export const initializeExploreAction = actionCreatorFactory<InitializeExplorePayload>(
|
||||
'explore/INITIALIZE_EXPLORE'
|
||||
).create();
|
||||
|
||||
/**
|
||||
* Initialize the wrapper split state
|
||||
*/
|
||||
export const initializeExploreSplitAction = noPayloadActionCreatorFactory('explore/INITIALIZE_EXPLORE_SPLIT').create();
|
||||
|
||||
/**
|
||||
* Display an error that happened during the selection of a datasource
|
||||
*/
|
||||
export const loadDatasourceFailureAction = actionCreatorFactory<LoadDatasourceFailurePayload>(
|
||||
'explore/LOAD_DATASOURCE_FAILURE'
|
||||
).create();
|
||||
|
||||
/**
|
||||
* Display an error when no datasources have been configured
|
||||
*/
|
||||
export const loadDatasourceMissingAction = actionCreatorFactory<LoadDatasourceMissingPayload>(
|
||||
'explore/LOAD_DATASOURCE_MISSING'
|
||||
).create();
|
||||
|
||||
/**
|
||||
* Start the async process of loading a datasource to display a loading indicator
|
||||
*/
|
||||
export const loadDatasourcePendingAction = actionCreatorFactory<LoadDatasourcePendingPayload>(
|
||||
'explore/LOAD_DATASOURCE_PENDING'
|
||||
).create();
|
||||
|
||||
/**
|
||||
* Datasource loading was successfully completed. The instance is stored in the state as well in case we need to
|
||||
* run datasource-specific code. Existing queries are imported to the new datasource if an importer exists,
|
||||
* e.g., Prometheus -> Loki queries.
|
||||
*/
|
||||
export const loadDatasourceSuccessAction = actionCreatorFactory<LoadDatasourceSuccessPayload>(
|
||||
'explore/LOAD_DATASOURCE_SUCCESS'
|
||||
).create();
|
||||
|
||||
/**
|
||||
* Action to modify a query given a datasource-specific modifier action.
|
||||
* @param exploreId Explore area
|
||||
* @param modification Action object with a type, e.g., ADD_FILTER
|
||||
* @param index Optional query row index. If omitted, the modification is applied to all query rows.
|
||||
* @param modifier Function that executes the modification, typically `datasourceInstance.modifyQueries`.
|
||||
*/
|
||||
export const modifyQueriesAction = actionCreatorFactory<ModifyQueriesPayload>('explore/MODIFY_QUERIES').create();
|
||||
|
||||
/**
|
||||
* Mark a query transaction as failed with an error extracted from the query response.
|
||||
* The transaction will be marked as `done`.
|
||||
*/
|
||||
export const queryTransactionFailureAction = actionCreatorFactory<QueryTransactionFailurePayload>(
|
||||
'explore/QUERY_TRANSACTION_FAILURE'
|
||||
).create();
|
||||
|
||||
/**
|
||||
* Start a query transaction for the given result type.
|
||||
* @param exploreId Explore area
|
||||
* @param transaction Query options and `done` status.
|
||||
* @param resultType Associate the transaction with a result viewer, e.g., Graph
|
||||
* @param rowIndex Index is used to associate latency for this transaction with a query row
|
||||
*/
|
||||
export const queryTransactionStartAction = actionCreatorFactory<QueryTransactionStartPayload>(
|
||||
'explore/QUERY_TRANSACTION_START'
|
||||
).create();
|
||||
|
||||
/**
|
||||
* Complete a query transaction, mark the transaction as `done` and store query state in URL.
|
||||
* If the transaction was started by a scanner, it keeps on scanning for more results.
|
||||
* Side-effect: the query is stored in localStorage.
|
||||
* @param exploreId Explore area
|
||||
* @param transactionId ID
|
||||
* @param result Response from `datasourceInstance.query()`
|
||||
* @param latency Duration between request and response
|
||||
* @param queries Queries from all query rows
|
||||
* @param datasourceId Origin datasource instance, used to discard results if current datasource is different
|
||||
*/
|
||||
export const queryTransactionSuccessAction = actionCreatorFactory<QueryTransactionSuccessPayload>(
|
||||
'explore/QUERY_TRANSACTION_SUCCESS'
|
||||
).create();
|
||||
|
||||
/**
|
||||
* Remove query row of the given index, as well as associated query results.
|
||||
*/
|
||||
export const removeQueryRowAction = actionCreatorFactory<RemoveQueryRowPayload>('explore/REMOVE_QUERY_ROW').create();
|
||||
export const runQueriesAction = noPayloadActionCreatorFactory('explore/RUN_QUERIES').create();
|
||||
export const runQueriesEmptyAction = actionCreatorFactory<RunQueriesEmptyPayload>('explore/RUN_QUERIES_EMPTY').create();
|
||||
|
||||
/**
|
||||
* Start a scan for more results using the given scanner.
|
||||
* @param exploreId Explore area
|
||||
* @param scanner Function that a) returns a new time range and b) triggers a query run for the new range
|
||||
*/
|
||||
export const scanStartAction = actionCreatorFactory<ScanStartPayload>('explore/SCAN_START').create();
|
||||
export const scanRangeAction = actionCreatorFactory<ScanRangePayload>('explore/SCAN_RANGE').create();
|
||||
|
||||
/**
|
||||
* Stop any scanning for more results.
|
||||
*/
|
||||
export const scanStopAction = actionCreatorFactory<ScanStopPayload>('explore/SCAN_STOP').create();
|
||||
|
||||
/**
|
||||
* Reset queries to the given queries. Any modifications will be discarded.
|
||||
* Use this action for clicks on query examples. Triggers a query run.
|
||||
*/
|
||||
export const setQueriesAction = actionCreatorFactory<SetQueriesPayload>('explore/SET_QUERIES').create();
|
||||
|
||||
/**
|
||||
* Close the split view and save URL state.
|
||||
*/
|
||||
export const splitCloseAction = noPayloadActionCreatorFactory('explore/SPLIT_CLOSE').create();
|
||||
|
||||
/**
|
||||
* Open the split view and copy the left state to be the right state.
|
||||
* The right state is automatically initialized.
|
||||
* The copy keeps all query modifications but wipes the query results.
|
||||
*/
|
||||
export const splitOpenAction = actionCreatorFactory<SplitOpenPayload>('explore/SPLIT_OPEN').create();
|
||||
export const stateSaveAction = noPayloadActionCreatorFactory('explore/STATE_SAVE').create();
|
||||
|
||||
/**
|
||||
* Expand/collapse the table result viewer. When collapsed, table queries won't be run.
|
||||
*/
|
||||
export const toggleTableAction = actionCreatorFactory<ToggleTablePayload>('explore/TOGGLE_TABLE').create();
|
||||
|
||||
/**
|
||||
* Expand/collapse the graph result viewer. When collapsed, graph queries won't be run.
|
||||
*/
|
||||
export const toggleGraphAction = actionCreatorFactory<ToggleGraphPayload>('explore/TOGGLE_GRAPH').create();
|
||||
|
||||
/**
|
||||
* Expand/collapse the logs result viewer. When collapsed, log queries won't be run.
|
||||
*/
|
||||
export const toggleLogsAction = actionCreatorFactory<ToggleLogsPayload>('explore/TOGGLE_LOGS').create();
|
||||
|
||||
/**
|
||||
* Updates datasource instance before datasouce loading has started
|
||||
*/
|
||||
export const updateDatasourceInstanceAction = actionCreatorFactory<UpdateDatasourceInstancePayload>(
|
||||
'explore/UPDATE_DATASOURCE_INSTANCE'
|
||||
).create();
|
||||
|
||||
/**
|
||||
* Resets state for explore.
|
||||
*/
|
||||
export const resetExploreAction = noPayloadActionCreatorFactory('explore/RESET_EXPLORE').create();
|
||||
export const queriesImportedAction = actionCreatorFactory<QueriesImportedPayload>('explore/QueriesImported').create();
|
||||
|
||||
export type HigherOrderAction =
|
||||
| InitializeExploreSplitAction
|
||||
| LoadDatasourceFailureAction
|
||||
| LoadDatasourceMissingAction
|
||||
| LoadDatasourcePendingAction
|
||||
| LoadDatasourceSuccessAction
|
||||
| ModifyQueriesAction
|
||||
| QueryTransactionFailureAction
|
||||
| QueryTransactionStartAction
|
||||
| QueryTransactionSuccessAction
|
||||
| RemoveQueryRowAction
|
||||
| RunQueriesEmptyAction
|
||||
| ScanRangeAction
|
||||
| ScanStartAction
|
||||
| ScanStopAction
|
||||
| SetQueriesAction
|
||||
| SplitCloseAction
|
||||
| SplitOpenAction
|
||||
| ToggleGraphAction
|
||||
| ToggleLogsAction
|
||||
| ToggleTableAction
|
||||
| UpdateDatasourceInstanceAction
|
||||
| ResetExploreAction
|
||||
| QueriesImported;
|
||||
| ActionOf<any>;
|
||||
|
||||
export type Action =
|
||||
| ActionOf<AddQueryRowPayload>
|
||||
| ActionOf<ChangeQueryPayload>
|
||||
| ActionOf<ChangeSizePayload>
|
||||
| ActionOf<ChangeTimePayload>
|
||||
| ActionOf<ClearQueriesPayload>
|
||||
| ActionOf<HighlightLogsExpressionPayload>
|
||||
| ActionOf<InitializeExplorePayload>
|
||||
| ActionOf<LoadDatasourceFailurePayload>
|
||||
| ActionOf<LoadDatasourceMissingPayload>
|
||||
| ActionOf<LoadDatasourcePendingPayload>
|
||||
| ActionOf<LoadDatasourceSuccessPayload>
|
||||
| ActionOf<ModifyQueriesPayload>
|
||||
| ActionOf<QueryTransactionFailurePayload>
|
||||
| ActionOf<QueryTransactionStartPayload>
|
||||
| ActionOf<QueryTransactionSuccessPayload>
|
||||
| ActionOf<RemoveQueryRowPayload>
|
||||
| ActionOf<RunQueriesEmptyPayload>
|
||||
| ActionOf<ScanStartPayload>
|
||||
| ActionOf<ScanRangePayload>
|
||||
| ActionOf<SetQueriesPayload>
|
||||
| ActionOf<SplitOpenPayload>
|
||||
| ActionOf<ToggleTablePayload>
|
||||
| ActionOf<ToggleGraphPayload>
|
||||
| ActionOf<ToggleLogsPayload>
|
||||
| ActionOf<UpdateDatasourceInstancePayload>
|
||||
| ActionOf<QueriesImportedPayload>;
|
||||
|
@ -30,41 +30,54 @@ import {
|
||||
DataQuery,
|
||||
DataSourceSelectItem,
|
||||
QueryHint,
|
||||
QueryFixAction,
|
||||
} from '@grafana/ui/src/types';
|
||||
import { ExploreId, ExploreUrlState, RangeScanner, ResultType, QueryOptions, ExploreUIState } from 'app/types/explore';
|
||||
import {
|
||||
ExploreId,
|
||||
ExploreUrlState,
|
||||
RangeScanner,
|
||||
ResultType,
|
||||
QueryOptions,
|
||||
QueryTransaction,
|
||||
ExploreUIState,
|
||||
} from 'app/types/explore';
|
||||
|
||||
import {
|
||||
Action as ThunkableAction,
|
||||
ActionTypes,
|
||||
AddQueryRowAction,
|
||||
ChangeSizeAction,
|
||||
HighlightLogsExpressionAction,
|
||||
LoadDatasourceFailureAction,
|
||||
LoadDatasourceMissingAction,
|
||||
LoadDatasourcePendingAction,
|
||||
LoadDatasourceSuccessAction,
|
||||
QueryTransactionStartAction,
|
||||
ScanStopAction,
|
||||
UpdateDatasourceInstanceAction,
|
||||
QueriesImported,
|
||||
Action,
|
||||
updateDatasourceInstanceAction,
|
||||
changeQueryAction,
|
||||
changeSizeAction,
|
||||
ChangeSizePayload,
|
||||
changeTimeAction,
|
||||
scanStopAction,
|
||||
clearQueriesAction,
|
||||
initializeExploreAction,
|
||||
loadDatasourceMissingAction,
|
||||
loadDatasourceFailureAction,
|
||||
loadDatasourcePendingAction,
|
||||
queriesImportedAction,
|
||||
LoadDatasourceSuccessPayload,
|
||||
loadDatasourceSuccessAction,
|
||||
modifyQueriesAction,
|
||||
queryTransactionFailureAction,
|
||||
queryTransactionStartAction,
|
||||
queryTransactionSuccessAction,
|
||||
scanRangeAction,
|
||||
runQueriesEmptyAction,
|
||||
scanStartAction,
|
||||
setQueriesAction,
|
||||
splitCloseAction,
|
||||
splitOpenAction,
|
||||
addQueryRowAction,
|
||||
AddQueryRowPayload,
|
||||
toggleGraphAction,
|
||||
toggleLogsAction,
|
||||
toggleTableAction,
|
||||
ToggleGraphPayload,
|
||||
ToggleLogsPayload,
|
||||
ToggleTablePayload,
|
||||
} from './actionTypes';
|
||||
import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory';
|
||||
|
||||
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, ThunkableAction>;
|
||||
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
|
||||
|
||||
/**
|
||||
* Adds a query row after the row with the given index.
|
||||
*/
|
||||
export function addQueryRow(exploreId: ExploreId, index: number): AddQueryRowAction {
|
||||
// /**
|
||||
// * Adds a query row after the row with the given index.
|
||||
// */
|
||||
export function addQueryRow(exploreId: ExploreId, index: number): ActionOf<AddQueryRowPayload> {
|
||||
const query = generateEmptyQuery(index + 1);
|
||||
return { type: ActionTypes.AddQueryRow, payload: { exploreId, index, query } };
|
||||
return addQueryRowAction({ exploreId, index, query });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,11 +87,11 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun
|
||||
return async (dispatch, getState) => {
|
||||
const newDataSourceInstance = await getDatasourceSrv().get(datasource);
|
||||
const currentDataSourceInstance = getState().explore[exploreId].datasourceInstance;
|
||||
const modifiedQueries = getState().explore[exploreId].modifiedQueries;
|
||||
const queries = getState().explore[exploreId].queries;
|
||||
|
||||
await dispatch(importQueries(exploreId, modifiedQueries, currentDataSourceInstance, newDataSourceInstance));
|
||||
await dispatch(importQueries(exploreId, queries, currentDataSourceInstance, newDataSourceInstance));
|
||||
|
||||
dispatch(updateDatasourceInstance(exploreId, newDataSourceInstance));
|
||||
dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: newDataSourceInstance }));
|
||||
|
||||
try {
|
||||
await dispatch(loadDatasource(exploreId, newDataSourceInstance));
|
||||
@ -107,7 +120,7 @@ export function changeQuery(
|
||||
query = { ...generateEmptyQuery(index) };
|
||||
}
|
||||
|
||||
dispatch({ type: ActionTypes.ChangeQuery, payload: { exploreId, query, index, override } });
|
||||
dispatch(changeQueryAction({ exploreId, query, index, override }));
|
||||
if (override) {
|
||||
dispatch(runQueries(exploreId));
|
||||
}
|
||||
@ -121,8 +134,8 @@ export function changeQuery(
|
||||
export function changeSize(
|
||||
exploreId: ExploreId,
|
||||
{ height, width }: { height: number; width: number }
|
||||
): ChangeSizeAction {
|
||||
return { type: ActionTypes.ChangeSize, payload: { exploreId, height, width } };
|
||||
): ActionOf<ChangeSizePayload> {
|
||||
return changeSizeAction({ exploreId, height, width });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,7 +143,7 @@ export function changeSize(
|
||||
*/
|
||||
export function changeTime(exploreId: ExploreId, range: TimeRange): ThunkResult<void> {
|
||||
return dispatch => {
|
||||
dispatch({ type: ActionTypes.ChangeTime, payload: { exploreId, range } });
|
||||
dispatch(changeTimeAction({ exploreId, range }));
|
||||
dispatch(runQueries(exploreId));
|
||||
};
|
||||
}
|
||||
@ -140,19 +153,12 @@ export function changeTime(exploreId: ExploreId, range: TimeRange): ThunkResult<
|
||||
*/
|
||||
export function clearQueries(exploreId: ExploreId): ThunkResult<void> {
|
||||
return dispatch => {
|
||||
dispatch(scanStop(exploreId));
|
||||
dispatch({ type: ActionTypes.ClearQueries, payload: { exploreId } });
|
||||
dispatch(scanStopAction({ exploreId }));
|
||||
dispatch(clearQueriesAction({ exploreId }));
|
||||
dispatch(stateSave());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight expressions in the log results
|
||||
*/
|
||||
export function highlightLogsExpression(exploreId: ExploreId, expressions: string[]): HighlightLogsExpressionAction {
|
||||
return { type: ActionTypes.HighlightLogsExpression, payload: { exploreId, expressions } };
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize Explore state with state from the URL and the React component.
|
||||
* Call this only on components for with the Explore state has not been initialized.
|
||||
@ -175,19 +181,17 @@ export function initializeExplore(
|
||||
meta: ds.meta,
|
||||
}));
|
||||
|
||||
dispatch({
|
||||
type: ActionTypes.InitializeExplore,
|
||||
payload: {
|
||||
dispatch(
|
||||
initializeExploreAction({
|
||||
exploreId,
|
||||
containerWidth,
|
||||
datasourceName,
|
||||
eventBridge,
|
||||
exploreDatasources,
|
||||
queries,
|
||||
range,
|
||||
ui,
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
if (exploreDatasources.length >= 1) {
|
||||
let instance;
|
||||
@ -204,7 +208,7 @@ export function initializeExplore(
|
||||
instance = await getDatasourceSrv().get();
|
||||
}
|
||||
|
||||
dispatch(updateDatasourceInstance(exploreId, instance));
|
||||
dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: instance }));
|
||||
|
||||
try {
|
||||
await dispatch(loadDatasource(exploreId, instance));
|
||||
@ -214,69 +218,17 @@ export function initializeExplore(
|
||||
}
|
||||
dispatch(runQueries(exploreId, true));
|
||||
} else {
|
||||
dispatch(loadDatasourceMissing(exploreId));
|
||||
dispatch(loadDatasourceMissingAction({ exploreId }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the wrapper split state
|
||||
*/
|
||||
export function initializeExploreSplit() {
|
||||
return async dispatch => {
|
||||
dispatch({ type: ActionTypes.InitializeExploreSplit });
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an error that happened during the selection of a datasource
|
||||
*/
|
||||
export const loadDatasourceFailure = (exploreId: ExploreId, error: string): LoadDatasourceFailureAction => ({
|
||||
type: ActionTypes.LoadDatasourceFailure,
|
||||
payload: {
|
||||
exploreId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Display an error when no datasources have been configured
|
||||
*/
|
||||
export const loadDatasourceMissing = (exploreId: ExploreId): LoadDatasourceMissingAction => ({
|
||||
type: ActionTypes.LoadDatasourceMissing,
|
||||
payload: { exploreId },
|
||||
});
|
||||
|
||||
/**
|
||||
* Start the async process of loading a datasource to display a loading indicator
|
||||
*/
|
||||
export const loadDatasourcePending = (
|
||||
exploreId: ExploreId,
|
||||
requestedDatasourceName: string
|
||||
): LoadDatasourcePendingAction => ({
|
||||
type: ActionTypes.LoadDatasourcePending,
|
||||
payload: {
|
||||
exploreId,
|
||||
requestedDatasourceName,
|
||||
},
|
||||
});
|
||||
|
||||
export const queriesImported = (exploreId: ExploreId, queries: DataQuery[]): QueriesImported => {
|
||||
return {
|
||||
type: ActionTypes.QueriesImported,
|
||||
payload: {
|
||||
exploreId,
|
||||
queries,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Datasource loading was successfully completed. The instance is stored in the state as well in case we need to
|
||||
* run datasource-specific code. Existing queries are imported to the new datasource if an importer exists,
|
||||
* e.g., Prometheus -> Loki queries.
|
||||
*/
|
||||
export const loadDatasourceSuccess = (exploreId: ExploreId, instance: any): LoadDatasourceSuccessAction => {
|
||||
export const loadDatasourceSuccess = (exploreId: ExploreId, instance: any): ActionOf<LoadDatasourceSuccessPayload> => {
|
||||
// Capabilities
|
||||
const supportsGraph = instance.meta.metrics;
|
||||
const supportsLogs = instance.meta.logs;
|
||||
@ -289,37 +241,18 @@ export const loadDatasourceSuccess = (exploreId: ExploreId, instance: any): Load
|
||||
// Save last-used datasource
|
||||
store.set(LAST_USED_DATASOURCE_KEY, instance.name);
|
||||
|
||||
return {
|
||||
type: ActionTypes.LoadDatasourceSuccess,
|
||||
payload: {
|
||||
exploreId,
|
||||
StartPage,
|
||||
datasourceInstance: instance,
|
||||
history,
|
||||
showingStartPage: Boolean(StartPage),
|
||||
supportsGraph,
|
||||
supportsLogs,
|
||||
supportsTable,
|
||||
},
|
||||
};
|
||||
return loadDatasourceSuccessAction({
|
||||
exploreId,
|
||||
StartPage,
|
||||
datasourceInstance: instance,
|
||||
history,
|
||||
showingStartPage: Boolean(StartPage),
|
||||
supportsGraph,
|
||||
supportsLogs,
|
||||
supportsTable,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates datasource instance before datasouce loading has started
|
||||
*/
|
||||
export function updateDatasourceInstance(
|
||||
exploreId: ExploreId,
|
||||
instance: DataSourceApi
|
||||
): UpdateDatasourceInstanceAction {
|
||||
return {
|
||||
type: ActionTypes.UpdateDatasourceInstance,
|
||||
payload: {
|
||||
exploreId,
|
||||
datasourceInstance: instance,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function importQueries(
|
||||
exploreId: ExploreId,
|
||||
queries: DataQuery[],
|
||||
@ -341,11 +274,11 @@ export function importQueries(
|
||||
}
|
||||
|
||||
const nextQueries = importedQueries.map((q, i) => ({
|
||||
...importedQueries[i],
|
||||
...q,
|
||||
...generateEmptyQuery(i),
|
||||
}));
|
||||
|
||||
dispatch(queriesImported(exploreId, nextQueries));
|
||||
dispatch(queriesImportedAction({ exploreId, queries: nextQueries }));
|
||||
};
|
||||
}
|
||||
|
||||
@ -357,7 +290,7 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
|
||||
const datasourceName = instance.name;
|
||||
|
||||
// Keep ID to track selection
|
||||
dispatch(loadDatasourcePending(exploreId, datasourceName));
|
||||
dispatch(loadDatasourcePendingAction({ exploreId, requestedDatasourceName: datasourceName }));
|
||||
let datasourceError = null;
|
||||
|
||||
try {
|
||||
@ -368,7 +301,7 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
|
||||
}
|
||||
|
||||
if (datasourceError) {
|
||||
dispatch(loadDatasourceFailure(exploreId, datasourceError));
|
||||
dispatch(loadDatasourceFailureAction({ exploreId, error: datasourceError }));
|
||||
return Promise.reject(`${datasourceName} loading failed`);
|
||||
}
|
||||
|
||||
@ -400,12 +333,12 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
|
||||
*/
|
||||
export function modifyQueries(
|
||||
exploreId: ExploreId,
|
||||
modification: any,
|
||||
modification: QueryFixAction,
|
||||
index: number,
|
||||
modifier: any
|
||||
): ThunkResult<void> {
|
||||
return dispatch => {
|
||||
dispatch({ type: ActionTypes.ModifyQueries, payload: { exploreId, modification, index, modifier } });
|
||||
dispatch(modifyQueriesAction({ exploreId, modification, index, modifier }));
|
||||
if (!modification.preventSubmit) {
|
||||
dispatch(runQueries(exploreId));
|
||||
}
|
||||
@ -470,29 +403,10 @@ export function queryTransactionFailure(
|
||||
return qt;
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: ActionTypes.QueryTransactionFailure,
|
||||
payload: { exploreId, queryTransactions: nextQueryTransactions },
|
||||
});
|
||||
dispatch(queryTransactionFailureAction({ exploreId, queryTransactions: nextQueryTransactions }));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a query transaction for the given result type.
|
||||
* @param exploreId Explore area
|
||||
* @param transaction Query options and `done` status.
|
||||
* @param resultType Associate the transaction with a result viewer, e.g., Graph
|
||||
* @param rowIndex Index is used to associate latency for this transaction with a query row
|
||||
*/
|
||||
export function queryTransactionStart(
|
||||
exploreId: ExploreId,
|
||||
transaction: QueryTransaction,
|
||||
resultType: ResultType,
|
||||
rowIndex: number
|
||||
): QueryTransactionStartAction {
|
||||
return { type: ActionTypes.QueryTransactionStart, payload: { exploreId, resultType, rowIndex, transaction } };
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a query transaction, mark the transaction as `done` and store query state in URL.
|
||||
* If the transaction was started by a scanner, it keeps on scanning for more results.
|
||||
@ -549,14 +463,13 @@ export function queryTransactionSuccess(
|
||||
// Side-effect: Saving history in localstorage
|
||||
const nextHistory = updateHistory(history, datasourceId, queries);
|
||||
|
||||
dispatch({
|
||||
type: ActionTypes.QueryTransactionSuccess,
|
||||
payload: {
|
||||
dispatch(
|
||||
queryTransactionSuccessAction({
|
||||
exploreId,
|
||||
history: nextHistory,
|
||||
queryTransactions: nextQueryTransactions,
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// Keep scanning for results if this was the last scanning transaction
|
||||
if (scanning) {
|
||||
@ -564,26 +477,16 @@ export function queryTransactionSuccess(
|
||||
const other = nextQueryTransactions.find(qt => qt.scanning && !qt.done);
|
||||
if (!other) {
|
||||
const range = scanner();
|
||||
dispatch({ type: ActionTypes.ScanRange, payload: { exploreId, range } });
|
||||
dispatch(scanRangeAction({ exploreId, range }));
|
||||
}
|
||||
} else {
|
||||
// We can stop scanning if we have a result
|
||||
dispatch(scanStop(exploreId));
|
||||
dispatch(scanStopAction({ exploreId }));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove query row of the given index, as well as associated query results.
|
||||
*/
|
||||
export function removeQueryRow(exploreId: ExploreId, index: number): ThunkResult<void> {
|
||||
return dispatch => {
|
||||
dispatch({ type: ActionTypes.RemoveQueryRow, payload: { exploreId, index } });
|
||||
dispatch(runQueries(exploreId));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Main action to run queries and dispatches sub-actions based on which result viewers are active
|
||||
*/
|
||||
@ -591,7 +494,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false) {
|
||||
return (dispatch, getState) => {
|
||||
const {
|
||||
datasourceInstance,
|
||||
modifiedQueries,
|
||||
queries,
|
||||
showingLogs,
|
||||
showingGraph,
|
||||
showingTable,
|
||||
@ -600,8 +503,8 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false) {
|
||||
supportsTable,
|
||||
} = getState().explore[exploreId];
|
||||
|
||||
if (!hasNonEmptyQuery(modifiedQueries)) {
|
||||
dispatch({ type: ActionTypes.RunQueriesEmpty, payload: { exploreId } });
|
||||
if (!hasNonEmptyQuery(queries)) {
|
||||
dispatch(runQueriesEmptyAction({ exploreId }));
|
||||
dispatch(stateSave()); // Remember to saves to state and update location
|
||||
return;
|
||||
}
|
||||
@ -662,14 +565,7 @@ function runQueriesForType(
|
||||
resultGetter?: any
|
||||
) {
|
||||
return async (dispatch, getState) => {
|
||||
const {
|
||||
datasourceInstance,
|
||||
eventBridge,
|
||||
modifiedQueries: queries,
|
||||
queryIntervals,
|
||||
range,
|
||||
scanning,
|
||||
} = getState().explore[exploreId];
|
||||
const { datasourceInstance, eventBridge, queries, queryIntervals, range, scanning } = getState().explore[exploreId];
|
||||
const datasourceId = datasourceInstance.meta.id;
|
||||
|
||||
// Run all queries concurrently
|
||||
@ -683,7 +579,7 @@ function runQueriesForType(
|
||||
queryIntervals,
|
||||
scanning
|
||||
);
|
||||
dispatch(queryTransactionStart(exploreId, transaction, resultType, rowIndex));
|
||||
dispatch(queryTransactionStartAction({ exploreId, resultType, rowIndex, transaction }));
|
||||
try {
|
||||
const now = Date.now();
|
||||
const res = await datasourceInstance.query(transaction.options);
|
||||
@ -707,21 +603,14 @@ function runQueriesForType(
|
||||
export function scanStart(exploreId: ExploreId, scanner: RangeScanner): ThunkResult<void> {
|
||||
return dispatch => {
|
||||
// Register the scanner
|
||||
dispatch({ type: ActionTypes.ScanStart, payload: { exploreId, scanner } });
|
||||
dispatch(scanStartAction({ exploreId, scanner }));
|
||||
// Scanning must trigger query run, and return the new range
|
||||
const range = scanner();
|
||||
// Set the new range to be displayed
|
||||
dispatch({ type: ActionTypes.ScanRange, payload: { exploreId, range } });
|
||||
dispatch(scanRangeAction({ exploreId, range }));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop any scanning for more results.
|
||||
*/
|
||||
export function scanStop(exploreId: ExploreId): ScanStopAction {
|
||||
return { type: ActionTypes.ScanStop, payload: { exploreId } };
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset queries to the given queries. Any modifications will be discarded.
|
||||
* Use this action for clicks on query examples. Triggers a query run.
|
||||
@ -730,13 +619,7 @@ export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): Thunk
|
||||
return dispatch => {
|
||||
// Inject react keys into query objects
|
||||
const queries = rawQueries.map(q => ({ ...q, ...generateEmptyQuery() }));
|
||||
dispatch({
|
||||
type: ActionTypes.SetQueries,
|
||||
payload: {
|
||||
exploreId,
|
||||
queries,
|
||||
},
|
||||
});
|
||||
dispatch(setQueriesAction({ exploreId, queries }));
|
||||
dispatch(runQueries(exploreId));
|
||||
};
|
||||
}
|
||||
@ -746,7 +629,7 @@ export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): Thunk
|
||||
*/
|
||||
export function splitClose(): ThunkResult<void> {
|
||||
return dispatch => {
|
||||
dispatch({ type: ActionTypes.SplitClose });
|
||||
dispatch(splitCloseAction());
|
||||
dispatch(stateSave());
|
||||
};
|
||||
}
|
||||
@ -763,9 +646,9 @@ export function splitOpen(): ThunkResult<void> {
|
||||
const itemState = {
|
||||
...leftState,
|
||||
queryTransactions: [],
|
||||
initialQueries: leftState.modifiedQueries.slice(),
|
||||
queries: leftState.queries.slice(),
|
||||
};
|
||||
dispatch({ type: ActionTypes.SplitOpen, payload: { itemState } });
|
||||
dispatch(splitOpenAction({ itemState }));
|
||||
dispatch(stateSave());
|
||||
};
|
||||
}
|
||||
@ -780,7 +663,7 @@ export function stateSave() {
|
||||
const urlStates: { [index: string]: string } = {};
|
||||
const leftUrlState: ExploreUrlState = {
|
||||
datasource: left.datasourceInstance.name,
|
||||
queries: left.modifiedQueries.map(clearQueryKeys),
|
||||
queries: left.queries.map(clearQueryKeys),
|
||||
range: left.range,
|
||||
ui: {
|
||||
showingGraph: left.showingGraph,
|
||||
@ -792,13 +675,9 @@ export function stateSave() {
|
||||
if (split) {
|
||||
const rightUrlState: ExploreUrlState = {
|
||||
datasource: right.datasourceInstance.name,
|
||||
queries: right.modifiedQueries.map(clearQueryKeys),
|
||||
queries: right.queries.map(clearQueryKeys),
|
||||
range: right.range,
|
||||
ui: {
|
||||
showingGraph: right.showingGraph,
|
||||
showingLogs: right.showingLogs,
|
||||
showingTable: right.showingTable,
|
||||
},
|
||||
ui: { showingGraph: right.showingGraph, showingLogs: right.showingLogs, showingTable: right.showingTable },
|
||||
};
|
||||
|
||||
urlStates.right = serializeStateToUrlParam(rightUrlState, true);
|
||||
@ -812,22 +691,25 @@ export function stateSave() {
|
||||
* Creates action to collapse graph/logs/table panel. When panel is collapsed,
|
||||
* queries won't be run
|
||||
*/
|
||||
const togglePanelActionCreator = (type: ActionTypes.ToggleGraph | ActionTypes.ToggleTable | ActionTypes.ToggleLogs) => (
|
||||
exploreId: ExploreId
|
||||
) => {
|
||||
const togglePanelActionCreator = (
|
||||
actionCreator:
|
||||
| ActionCreator<ToggleGraphPayload>
|
||||
| ActionCreator<ToggleLogsPayload>
|
||||
| ActionCreator<ToggleTablePayload>
|
||||
) => (exploreId: ExploreId) => {
|
||||
return (dispatch, getState) => {
|
||||
let shouldRunQueries;
|
||||
dispatch({ type, payload: { exploreId } });
|
||||
dispatch(actionCreator({ exploreId }));
|
||||
dispatch(stateSave());
|
||||
|
||||
switch (type) {
|
||||
case ActionTypes.ToggleGraph:
|
||||
switch (actionCreator.type) {
|
||||
case toggleGraphAction.type:
|
||||
shouldRunQueries = getState().explore[exploreId].showingGraph;
|
||||
break;
|
||||
case ActionTypes.ToggleLogs:
|
||||
case toggleLogsAction.type:
|
||||
shouldRunQueries = getState().explore[exploreId].showingLogs;
|
||||
break;
|
||||
case ActionTypes.ToggleTable:
|
||||
case toggleTableAction.type:
|
||||
shouldRunQueries = getState().explore[exploreId].showingTable;
|
||||
break;
|
||||
}
|
||||
@ -841,23 +723,14 @@ const togglePanelActionCreator = (type: ActionTypes.ToggleGraph | ActionTypes.To
|
||||
/**
|
||||
* Expand/collapse the graph result viewer. When collapsed, graph queries won't be run.
|
||||
*/
|
||||
export const toggleGraph = togglePanelActionCreator(ActionTypes.ToggleGraph);
|
||||
export const toggleGraph = togglePanelActionCreator(toggleGraphAction);
|
||||
|
||||
/**
|
||||
* Expand/collapse the logs result viewer. When collapsed, log queries won't be run.
|
||||
*/
|
||||
export const toggleLogs = togglePanelActionCreator(ActionTypes.ToggleLogs);
|
||||
export const toggleLogs = togglePanelActionCreator(toggleLogsAction);
|
||||
|
||||
/**
|
||||
* Expand/collapse the table result viewer. When collapsed, table queries won't be run.
|
||||
*/
|
||||
export const toggleTable = togglePanelActionCreator(ActionTypes.ToggleTable);
|
||||
|
||||
/**
|
||||
* Resets state for explore.
|
||||
*/
|
||||
export function resetExplore(): ThunkResult<void> {
|
||||
return dispatch => {
|
||||
dispatch({ type: ActionTypes.ResetExplore, payload: {} });
|
||||
};
|
||||
}
|
||||
export const toggleTable = togglePanelActionCreator(toggleTableAction);
|
||||
|
@ -1,42 +1,47 @@
|
||||
import { Action, ActionTypes } from './actionTypes';
|
||||
import { itemReducer, makeExploreItemState } from './reducers';
|
||||
import { ExploreId } from 'app/types/explore';
|
||||
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
||||
import { reducerTester } from 'test/core/redux/reducerTester';
|
||||
import { scanStartAction, scanStopAction } from './actionTypes';
|
||||
import { Reducer } from 'redux';
|
||||
import { ActionOf } from 'app/core/redux/actionCreatorFactory';
|
||||
|
||||
describe('Explore item reducer', () => {
|
||||
describe('scanning', () => {
|
||||
test('should start scanning', () => {
|
||||
let state = makeExploreItemState();
|
||||
const action: Action = {
|
||||
type: ActionTypes.ScanStart,
|
||||
payload: {
|
||||
exploreId: ExploreId.left,
|
||||
scanner: jest.fn(),
|
||||
},
|
||||
const scanner = jest.fn();
|
||||
const initalState = {
|
||||
...makeExploreItemState(),
|
||||
scanning: false,
|
||||
scanner: undefined,
|
||||
};
|
||||
state = itemReducer(state, action);
|
||||
expect(state.scanning).toBeTruthy();
|
||||
expect(state.scanner).toBe(action.payload.scanner);
|
||||
|
||||
reducerTester()
|
||||
.givenReducer(itemReducer as Reducer<ExploreItemState, ActionOf<any>>, initalState)
|
||||
.whenActionIsDispatched(scanStartAction({ exploreId: ExploreId.left, scanner }))
|
||||
.thenStateShouldEqual({
|
||||
...makeExploreItemState(),
|
||||
scanning: true,
|
||||
scanner,
|
||||
});
|
||||
});
|
||||
test('should stop scanning', () => {
|
||||
let state = makeExploreItemState();
|
||||
const start: Action = {
|
||||
type: ActionTypes.ScanStart,
|
||||
payload: {
|
||||
exploreId: ExploreId.left,
|
||||
scanner: jest.fn(),
|
||||
},
|
||||
const scanner = jest.fn();
|
||||
const initalState = {
|
||||
...makeExploreItemState(),
|
||||
scanning: true,
|
||||
scanner,
|
||||
scanRange: {},
|
||||
};
|
||||
state = itemReducer(state, start);
|
||||
expect(state.scanning).toBeTruthy();
|
||||
const action: Action = {
|
||||
type: ActionTypes.ScanStop,
|
||||
payload: {
|
||||
exploreId: ExploreId.left,
|
||||
},
|
||||
};
|
||||
state = itemReducer(state, action);
|
||||
expect(state.scanning).toBeFalsy();
|
||||
expect(state.scanner).toBeUndefined();
|
||||
|
||||
reducerTester()
|
||||
.givenReducer(itemReducer as Reducer<ExploreItemState, ActionOf<any>>, initalState)
|
||||
.whenActionIsDispatched(scanStopAction({ exploreId: ExploreId.left }))
|
||||
.thenStateShouldEqual({
|
||||
...makeExploreItemState(),
|
||||
scanning: false,
|
||||
scanner: undefined,
|
||||
scanRange: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -3,11 +3,41 @@ import {
|
||||
generateEmptyQuery,
|
||||
getIntervals,
|
||||
ensureQueries,
|
||||
getQueryKeys,
|
||||
} from 'app/core/utils/explore';
|
||||
import { ExploreItemState, ExploreState, QueryTransaction } from 'app/types/explore';
|
||||
import { DataQuery } from '@grafana/ui/src/types';
|
||||
|
||||
import { Action, ActionTypes } from './actionTypes';
|
||||
import { HigherOrderAction, ActionTypes } from './actionTypes';
|
||||
import { reducerFactory } from 'app/core/redux';
|
||||
import {
|
||||
addQueryRowAction,
|
||||
changeQueryAction,
|
||||
changeSizeAction,
|
||||
changeTimeAction,
|
||||
clearQueriesAction,
|
||||
highlightLogsExpressionAction,
|
||||
initializeExploreAction,
|
||||
updateDatasourceInstanceAction,
|
||||
loadDatasourceFailureAction,
|
||||
loadDatasourceMissingAction,
|
||||
loadDatasourcePendingAction,
|
||||
loadDatasourceSuccessAction,
|
||||
modifyQueriesAction,
|
||||
queryTransactionFailureAction,
|
||||
queryTransactionStartAction,
|
||||
queryTransactionSuccessAction,
|
||||
removeQueryRowAction,
|
||||
runQueriesEmptyAction,
|
||||
scanRangeAction,
|
||||
scanStartAction,
|
||||
scanStopAction,
|
||||
setQueriesAction,
|
||||
toggleGraphAction,
|
||||
toggleLogsAction,
|
||||
toggleTableAction,
|
||||
queriesImportedAction,
|
||||
} from './actionTypes';
|
||||
|
||||
export const DEFAULT_RANGE = {
|
||||
from: 'now-6h',
|
||||
@ -30,9 +60,8 @@ export const makeExploreItemState = (): ExploreItemState => ({
|
||||
datasourceMissing: false,
|
||||
exploreDatasources: [],
|
||||
history: [],
|
||||
initialQueries: [],
|
||||
queries: [],
|
||||
initialized: false,
|
||||
modifiedQueries: [],
|
||||
queryTransactions: [],
|
||||
queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL },
|
||||
range: DEFAULT_RANGE,
|
||||
@ -44,6 +73,7 @@ export const makeExploreItemState = (): ExploreItemState => ({
|
||||
supportsGraph: null,
|
||||
supportsLogs: null,
|
||||
supportsTable: null,
|
||||
queryKeys: [],
|
||||
});
|
||||
|
||||
/**
|
||||
@ -58,21 +88,15 @@ export const initialExploreState: ExploreState = {
|
||||
/**
|
||||
* Reducer for an Explore area, to be used by the global Explore reducer.
|
||||
*/
|
||||
export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.AddQueryRow: {
|
||||
const { initialQueries, modifiedQueries, queryTransactions } = state;
|
||||
export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemState)
|
||||
.addMapper({
|
||||
filter: addQueryRowAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { queries, queryTransactions } = state;
|
||||
const { index, query } = action.payload;
|
||||
|
||||
// Add new query row after given index, keep modifications of existing rows
|
||||
const nextModifiedQueries = [
|
||||
...modifiedQueries.slice(0, index + 1),
|
||||
{ ...query },
|
||||
...initialQueries.slice(index + 1),
|
||||
];
|
||||
|
||||
// Add to initialQueries, which will cause a new row to be rendered
|
||||
const nextQueries = [...initialQueries.slice(0, index + 1), { ...query }, ...initialQueries.slice(index + 1)];
|
||||
// Add to queries, which will cause a new row to be rendered
|
||||
const nextQueries = [...queries.slice(0, index + 1), { ...query }, ...queries.slice(index + 1)];
|
||||
|
||||
// Ongoing transactions need to update their row indices
|
||||
const nextQueryTransactions = queryTransactions.map(qt => {
|
||||
@ -87,48 +111,38 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
|
||||
return {
|
||||
...state,
|
||||
initialQueries: nextQueries,
|
||||
queries: nextQueries,
|
||||
logsHighlighterExpressions: undefined,
|
||||
modifiedQueries: nextModifiedQueries,
|
||||
queryTransactions: nextQueryTransactions,
|
||||
queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.ChangeQuery: {
|
||||
const { initialQueries, queryTransactions } = state;
|
||||
let { modifiedQueries } = state;
|
||||
const { query, index, override } = action.payload;
|
||||
|
||||
// Fast path: only change modifiedQueries to not trigger an update
|
||||
modifiedQueries[index] = query;
|
||||
if (!override) {
|
||||
return {
|
||||
...state,
|
||||
modifiedQueries,
|
||||
};
|
||||
}
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: changeQueryAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { queries, queryTransactions } = state;
|
||||
const { query, index } = action.payload;
|
||||
|
||||
// Override path: queries are completely reset
|
||||
const nextQuery: DataQuery = {
|
||||
...query,
|
||||
...generateEmptyQuery(index),
|
||||
};
|
||||
const nextQueries = [...initialQueries];
|
||||
const nextQuery: DataQuery = { ...query, ...generateEmptyQuery(index) };
|
||||
const nextQueries = [...queries];
|
||||
nextQueries[index] = nextQuery;
|
||||
modifiedQueries = [...nextQueries];
|
||||
|
||||
// Discard ongoing transaction related to row query
|
||||
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
||||
|
||||
return {
|
||||
...state,
|
||||
initialQueries: nextQueries,
|
||||
modifiedQueries: nextQueries.slice(),
|
||||
queries: nextQueries,
|
||||
queryTransactions: nextQueryTransactions,
|
||||
queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.ChangeSize: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: changeSizeAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { range, datasourceInstance } = state;
|
||||
let interval = '1s';
|
||||
if (datasourceInstance && datasourceInstance.interval) {
|
||||
@ -137,32 +151,37 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
const containerWidth = action.payload.width;
|
||||
const queryIntervals = getIntervals(range, interval, containerWidth);
|
||||
return { ...state, containerWidth, queryIntervals };
|
||||
}
|
||||
|
||||
case ActionTypes.ChangeTime: {
|
||||
return {
|
||||
...state,
|
||||
range: action.payload.range,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.ClearQueries: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: changeTimeAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
return { ...state, range: action.payload.range };
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: clearQueriesAction,
|
||||
mapper: (state): ExploreItemState => {
|
||||
const queries = ensureQueries();
|
||||
return {
|
||||
...state,
|
||||
initialQueries: queries.slice(),
|
||||
modifiedQueries: queries.slice(),
|
||||
queries: queries.slice(),
|
||||
queryTransactions: [],
|
||||
showingStartPage: Boolean(state.StartPage),
|
||||
queryKeys: getQueryKeys(queries, state.datasourceInstance),
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.HighlightLogsExpression: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: highlightLogsExpressionAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { expressions } = action.payload;
|
||||
return { ...state, logsHighlighterExpressions: expressions };
|
||||
}
|
||||
|
||||
case ActionTypes.InitializeExplore: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: initializeExploreAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { containerWidth, eventBridge, exploreDatasources, queries, range, ui } = action.payload;
|
||||
return {
|
||||
...state,
|
||||
@ -170,35 +189,41 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
eventBridge,
|
||||
exploreDatasources,
|
||||
range,
|
||||
initialQueries: queries,
|
||||
queries,
|
||||
initialized: true,
|
||||
modifiedQueries: queries.slice(),
|
||||
queryKeys: getQueryKeys(queries, state.datasourceInstance),
|
||||
...ui,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.UpdateDatasourceInstance: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: updateDatasourceInstanceAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { datasourceInstance } = action.payload;
|
||||
return {
|
||||
...state,
|
||||
datasourceInstance,
|
||||
datasourceName: datasourceInstance.name,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.LoadDatasourceFailure: {
|
||||
return { ...state, datasourceInstance, queryKeys: getQueryKeys(state.queries, datasourceInstance) };
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: loadDatasourceFailureAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
return { ...state, datasourceError: action.payload.error, datasourceLoading: false };
|
||||
}
|
||||
|
||||
case ActionTypes.LoadDatasourceMissing: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: loadDatasourceMissingAction,
|
||||
mapper: (state): ExploreItemState => {
|
||||
return { ...state, datasourceMissing: true, datasourceLoading: false };
|
||||
}
|
||||
|
||||
case ActionTypes.LoadDatasourcePending: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: loadDatasourcePendingAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
return { ...state, datasourceLoading: true, requestedDatasourceName: action.payload.requestedDatasourceName };
|
||||
}
|
||||
|
||||
case ActionTypes.LoadDatasourceSuccess: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: loadDatasourceSuccessAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { containerWidth, range } = state;
|
||||
const {
|
||||
StartPage,
|
||||
@ -227,32 +252,29 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
logsHighlighterExpressions: undefined,
|
||||
queryTransactions: [],
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.ModifyQueries: {
|
||||
const { initialQueries, modifiedQueries, queryTransactions } = state;
|
||||
const { modification, index, modifier } = action.payload as any;
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: modifyQueriesAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { queries, queryTransactions } = state;
|
||||
const { modification, index, modifier } = action.payload;
|
||||
let nextQueries: DataQuery[];
|
||||
let nextQueryTransactions;
|
||||
if (index === undefined) {
|
||||
// Modify all queries
|
||||
nextQueries = initialQueries.map((query, i) => ({
|
||||
...modifier(modifiedQueries[i], modification),
|
||||
nextQueries = queries.map((query, i) => ({
|
||||
...modifier({ ...query }, modification),
|
||||
...generateEmptyQuery(i),
|
||||
}));
|
||||
// Discard all ongoing transactions
|
||||
nextQueryTransactions = [];
|
||||
} else {
|
||||
// Modify query only at index
|
||||
nextQueries = initialQueries.map((query, i) => {
|
||||
nextQueries = queries.map((query, i) => {
|
||||
// Synchronize all queries with local query cache to ensure consistency
|
||||
// TODO still needed?
|
||||
return i === index
|
||||
? {
|
||||
...modifier(modifiedQueries[i], modification),
|
||||
...generateEmptyQuery(i),
|
||||
}
|
||||
: query;
|
||||
return i === index ? { ...modifier({ ...query }, modification), ...generateEmptyQuery(i) } : query;
|
||||
});
|
||||
nextQueryTransactions = queryTransactions
|
||||
// Consume the hint corresponding to the action
|
||||
@ -267,22 +289,22 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
initialQueries: nextQueries,
|
||||
modifiedQueries: nextQueries.slice(),
|
||||
queries: nextQueries,
|
||||
queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
|
||||
queryTransactions: nextQueryTransactions,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.QueryTransactionFailure: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: queryTransactionFailureAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { queryTransactions } = action.payload;
|
||||
return {
|
||||
...state,
|
||||
queryTransactions,
|
||||
showingStartPage: false,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.QueryTransactionStart: {
|
||||
return { ...state, queryTransactions, showingStartPage: false };
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: queryTransactionStartAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { queryTransactions } = state;
|
||||
const { resultType, rowIndex, transaction } = action.payload;
|
||||
// Discarding existing transactions of same type
|
||||
@ -293,14 +315,12 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
// Append new transaction
|
||||
const nextQueryTransactions: QueryTransaction[] = [...remainingTransactions, transaction];
|
||||
|
||||
return {
|
||||
...state,
|
||||
queryTransactions: nextQueryTransactions,
|
||||
showingStartPage: false,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.QueryTransactionSuccess: {
|
||||
return { ...state, queryTransactions: nextQueryTransactions, showingStartPage: false };
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: queryTransactionSuccessAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { datasourceInstance, queryIntervals } = state;
|
||||
const { history, queryTransactions } = action.payload;
|
||||
const results = calculateResultsFromQueryTransactions(
|
||||
@ -309,30 +329,24 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
queryIntervals.intervalMs
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
...results,
|
||||
history,
|
||||
queryTransactions,
|
||||
showingStartPage: false,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.RemoveQueryRow: {
|
||||
const { datasourceInstance, initialQueries, queryIntervals, queryTransactions } = state;
|
||||
let { modifiedQueries } = state;
|
||||
return { ...state, ...results, history, queryTransactions, showingStartPage: false };
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: removeQueryRowAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { datasourceInstance, queries, queryIntervals, queryTransactions, queryKeys } = state;
|
||||
const { index } = action.payload;
|
||||
|
||||
modifiedQueries = [...modifiedQueries.slice(0, index), ...modifiedQueries.slice(index + 1)];
|
||||
|
||||
if (initialQueries.length <= 1) {
|
||||
if (queries.length <= 1) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const nextQueries = [...initialQueries.slice(0, index), ...initialQueries.slice(index + 1)];
|
||||
const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
|
||||
const nextQueryKeys = [...queryKeys.slice(0, index), ...queryKeys.slice(index + 1)];
|
||||
|
||||
// Discard transactions related to row query
|
||||
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
||||
const nextQueryTransactions = queryTransactions.filter(qt => nextQueries.some(nq => nq.key === qt.query.key));
|
||||
const results = calculateResultsFromQueryTransactions(
|
||||
nextQueryTransactions,
|
||||
datasourceInstance,
|
||||
@ -342,26 +356,34 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
return {
|
||||
...state,
|
||||
...results,
|
||||
initialQueries: nextQueries,
|
||||
queries: nextQueries,
|
||||
logsHighlighterExpressions: undefined,
|
||||
modifiedQueries: nextQueries.slice(),
|
||||
queryTransactions: nextQueryTransactions,
|
||||
queryKeys: nextQueryKeys,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.RunQueriesEmpty: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: runQueriesEmptyAction,
|
||||
mapper: (state): ExploreItemState => {
|
||||
return { ...state, queryTransactions: [] };
|
||||
}
|
||||
|
||||
case ActionTypes.ScanRange: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: scanRangeAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
return { ...state, scanRange: action.payload.range };
|
||||
}
|
||||
|
||||
case ActionTypes.ScanStart: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: scanStartAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
return { ...state, scanning: true, scanner: action.payload.scanner };
|
||||
}
|
||||
|
||||
case ActionTypes.ScanStop: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: scanStopAction,
|
||||
mapper: (state): ExploreItemState => {
|
||||
const { queryTransactions } = state;
|
||||
const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done);
|
||||
return {
|
||||
@ -371,14 +393,22 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
scanRange: undefined,
|
||||
scanner: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.SetQueries: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: setQueriesAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { queries } = action.payload;
|
||||
return { ...state, initialQueries: queries.slice(), modifiedQueries: queries.slice() };
|
||||
}
|
||||
|
||||
case ActionTypes.ToggleGraph: {
|
||||
return {
|
||||
...state,
|
||||
queries: queries.slice(),
|
||||
queryKeys: getQueryKeys(queries, state.datasourceInstance),
|
||||
};
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: toggleGraphAction,
|
||||
mapper: (state): ExploreItemState => {
|
||||
const showingGraph = !state.showingGraph;
|
||||
let nextQueryTransactions = state.queryTransactions;
|
||||
if (!showingGraph) {
|
||||
@ -386,9 +416,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph');
|
||||
}
|
||||
return { ...state, queryTransactions: nextQueryTransactions, showingGraph };
|
||||
}
|
||||
|
||||
case ActionTypes.ToggleLogs: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: toggleLogsAction,
|
||||
mapper: (state): ExploreItemState => {
|
||||
const showingLogs = !state.showingLogs;
|
||||
let nextQueryTransactions = state.queryTransactions;
|
||||
if (!showingLogs) {
|
||||
@ -396,9 +428,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs');
|
||||
}
|
||||
return { ...state, queryTransactions: nextQueryTransactions, showingLogs };
|
||||
}
|
||||
|
||||
case ActionTypes.ToggleTable: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: toggleTableAction,
|
||||
mapper: (state): ExploreItemState => {
|
||||
const showingTable = !state.showingTable;
|
||||
if (showingTable) {
|
||||
return { ...state, showingTable, queryTransactions: state.queryTransactions };
|
||||
@ -413,25 +447,26 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
);
|
||||
|
||||
return { ...state, ...results, queryTransactions: nextQueryTransactions, showingTable };
|
||||
}
|
||||
|
||||
case ActionTypes.QueriesImported: {
|
||||
},
|
||||
})
|
||||
.addMapper({
|
||||
filter: queriesImportedAction,
|
||||
mapper: (state, action): ExploreItemState => {
|
||||
const { queries } = action.payload;
|
||||
return {
|
||||
...state,
|
||||
initialQueries: action.payload.queries,
|
||||
modifiedQueries: action.payload.queries.slice(),
|
||||
queries,
|
||||
queryKeys: getQueryKeys(queries, state.datasourceInstance),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
},
|
||||
})
|
||||
.create();
|
||||
|
||||
/**
|
||||
* Global Explore reducer that handles multiple Explore areas (left and right).
|
||||
* Actions that have an `exploreId` get routed to the ExploreItemReducer.
|
||||
*/
|
||||
export const exploreReducer = (state = initialExploreState, action: Action): ExploreState => {
|
||||
export const exploreReducer = (state = initialExploreState, action: HigherOrderAction): ExploreState => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.SplitClose: {
|
||||
return { ...state, split: false };
|
||||
@ -454,10 +489,7 @@ export const exploreReducer = (state = initialExploreState, action: Action): Exp
|
||||
const { exploreId } = action.payload as any;
|
||||
if (exploreId !== undefined) {
|
||||
const exploreItemState = state[exploreId];
|
||||
return {
|
||||
...state,
|
||||
[exploreId]: itemReducer(exploreItemState, action),
|
||||
};
|
||||
return { ...state, [exploreId]: itemReducer(exploreItemState, action) };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ export class LokiQueryEditor extends PureComponent<Props> {
|
||||
query: {
|
||||
...this.state.query,
|
||||
expr: query.expr,
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -59,14 +59,20 @@ export class LokiQueryEditor extends PureComponent<Props> {
|
||||
<div>
|
||||
<LokiQueryField
|
||||
datasource={datasource}
|
||||
initialQuery={query}
|
||||
query={query}
|
||||
onQueryChange={this.onFieldChange}
|
||||
onPressEnter={this.onRunQuery}
|
||||
onExecuteQuery={this.onRunQuery}
|
||||
history={[]}
|
||||
/>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<div className="gf-form-label">Format as</div>
|
||||
<Select isSearchable={false} options={formatOptions} onChange={this.onFormatChanged} value={currentFormat} />
|
||||
<Select
|
||||
isSearchable={false}
|
||||
options={formatOptions}
|
||||
onChange={this.onFormatChanged}
|
||||
value={currentFormat}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label gf-form-label--grow" />
|
||||
|
@ -12,12 +12,12 @@ import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explor
|
||||
import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom';
|
||||
import BracesPlugin from 'app/features/explore/slate-plugins/braces';
|
||||
import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
|
||||
import LokiDatasource from '../datasource';
|
||||
|
||||
// Types
|
||||
import { LokiQuery } from '../types';
|
||||
import { TypeaheadOutput } from 'app/types/explore';
|
||||
import { TypeaheadOutput, HistoryItem } from 'app/types/explore';
|
||||
import { makePromiseCancelable, CancelablePromise } from 'app/core/utils/CancelablePromise';
|
||||
import { ExploreDataSourceApi, ExploreQueryFieldProps } from '@grafana/ui';
|
||||
|
||||
const PRISM_SYNTAX = 'promql';
|
||||
|
||||
@ -65,15 +65,8 @@ interface CascaderOption {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface LokiQueryFieldProps {
|
||||
datasource: LokiDatasource;
|
||||
error?: string | JSX.Element;
|
||||
hint?: any;
|
||||
history?: any[];
|
||||
initialQuery?: LokiQuery;
|
||||
onClickHintFix?: (action: any) => void;
|
||||
onPressEnter?: () => void;
|
||||
onQueryChange?: (value: LokiQuery, override?: boolean) => void;
|
||||
interface LokiQueryFieldProps extends ExploreQueryFieldProps<ExploreDataSourceApi, LokiQuery> {
|
||||
history: HistoryItem[];
|
||||
}
|
||||
|
||||
interface LokiQueryFieldState {
|
||||
@ -98,14 +91,14 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
|
||||
|
||||
this.plugins = [
|
||||
BracesPlugin(),
|
||||
RunnerPlugin({ handler: props.onPressEnter }),
|
||||
RunnerPlugin({ handler: props.onExecuteQuery }),
|
||||
PluginPrism({
|
||||
onlyIn: node => node.type === 'code_block',
|
||||
getSyntax: node => 'promql',
|
||||
}),
|
||||
];
|
||||
|
||||
this.pluginsSearch = [RunnerPlugin({ handler: props.onPressEnter })];
|
||||
this.pluginsSearch = [RunnerPlugin({ handler: props.onExecuteQuery })];
|
||||
|
||||
this.state = {
|
||||
logLabelOptions: [],
|
||||
@ -169,20 +162,21 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
|
||||
|
||||
onChangeQuery = (value: string, override?: boolean) => {
|
||||
// Send text change to parent
|
||||
const { initialQuery, onQueryChange } = this.props;
|
||||
const { query, onQueryChange, onExecuteQuery } = this.props;
|
||||
if (onQueryChange) {
|
||||
const query = {
|
||||
...initialQuery,
|
||||
expr: value,
|
||||
};
|
||||
onQueryChange(query, override);
|
||||
const nextQuery = { ...query, expr: value };
|
||||
onQueryChange(nextQuery);
|
||||
|
||||
if (override && onExecuteQuery) {
|
||||
onExecuteQuery();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onClickHintFix = () => {
|
||||
const { hint, onClickHintFix } = this.props;
|
||||
if (onClickHintFix && hint && hint.fix) {
|
||||
onClickHintFix(hint.fix.action);
|
||||
const { hint, onExecuteHint } = this.props;
|
||||
if (onExecuteHint && hint && hint.fix) {
|
||||
onExecuteHint(hint.fix.action);
|
||||
}
|
||||
};
|
||||
|
||||
@ -220,7 +214,7 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
|
||||
};
|
||||
|
||||
render() {
|
||||
const { error, hint, initialQuery } = this.props;
|
||||
const { error, hint, query } = this.props;
|
||||
const { logLabelOptions, syntaxLoaded } = this.state;
|
||||
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
|
||||
const hasLogLabels = logLabelOptions && logLabelOptions.length > 0;
|
||||
@ -240,10 +234,11 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
|
||||
<QueryField
|
||||
additionalPlugins={this.plugins}
|
||||
cleanText={cleanText}
|
||||
initialQuery={initialQuery.expr}
|
||||
initialQuery={query.expr}
|
||||
onTypeahead={this.onTypeahead}
|
||||
onWillApplySuggestion={willApplySuggestion}
|
||||
onValueChanged={this.onChangeQuery}
|
||||
onQueryChange={this.onChangeQuery}
|
||||
onExecuteQuery={this.props.onExecuteQuery}
|
||||
placeholder="Enter a Loki query"
|
||||
portalOrigin="loki"
|
||||
syntaxLoaded={syntaxLoaded}
|
||||
|
@ -1,11 +1,8 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import LokiCheatSheet from './LokiCheatSheet';
|
||||
import { ExploreStartPageProps } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
onClickExample: () => void;
|
||||
}
|
||||
|
||||
export default class LokiStartPage extends PureComponent<Props> {
|
||||
export default class LokiStartPage extends PureComponent<ExploreStartPageProps> {
|
||||
render() {
|
||||
return (
|
||||
<div className="grafana-info-box grafana-info-box--max-lg">
|
||||
|
@ -4,7 +4,7 @@ import Cascader from 'rc-cascader';
|
||||
import PluginPrism from 'slate-prism';
|
||||
import Prism from 'prismjs';
|
||||
|
||||
import { TypeaheadOutput } from 'app/types/explore';
|
||||
import { TypeaheadOutput, HistoryItem } from 'app/types/explore';
|
||||
|
||||
// dom also includes Element polyfills
|
||||
import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom';
|
||||
@ -13,6 +13,7 @@ import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
|
||||
import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField';
|
||||
import { PromQuery } from '../types';
|
||||
import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise';
|
||||
import { ExploreDataSourceApi, ExploreQueryFieldProps } from '@grafana/ui';
|
||||
|
||||
const HISTOGRAM_GROUP = '__histograms__';
|
||||
const METRIC_MARK = 'metric';
|
||||
@ -86,15 +87,8 @@ interface CascaderOption {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface PromQueryFieldProps {
|
||||
datasource: any;
|
||||
error?: string | JSX.Element;
|
||||
initialQuery: PromQuery;
|
||||
hint?: any;
|
||||
history?: any[];
|
||||
onClickHintFix?: (action: any) => void;
|
||||
onPressEnter?: () => void;
|
||||
onQueryChange?: (value: PromQuery, override?: boolean) => void;
|
||||
interface PromQueryFieldProps extends ExploreQueryFieldProps<ExploreDataSourceApi, PromQuery> {
|
||||
history: HistoryItem[];
|
||||
}
|
||||
|
||||
interface PromQueryFieldState {
|
||||
@ -116,7 +110,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
||||
|
||||
this.plugins = [
|
||||
BracesPlugin(),
|
||||
RunnerPlugin({ handler: props.onPressEnter }),
|
||||
RunnerPlugin({ handler: props.onExecuteQuery }),
|
||||
PluginPrism({
|
||||
onlyIn: node => node.type === 'code_block',
|
||||
getSyntax: node => 'promql',
|
||||
@ -174,20 +168,21 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
||||
|
||||
onChangeQuery = (value: string, override?: boolean) => {
|
||||
// Send text change to parent
|
||||
const { initialQuery, onQueryChange } = this.props;
|
||||
const { query, onQueryChange, onExecuteQuery } = this.props;
|
||||
if (onQueryChange) {
|
||||
const query: PromQuery = {
|
||||
...initialQuery,
|
||||
expr: value,
|
||||
};
|
||||
onQueryChange(query, override);
|
||||
const nextQuery: PromQuery = { ...query, expr: value };
|
||||
onQueryChange(nextQuery);
|
||||
|
||||
if (override && onExecuteQuery) {
|
||||
onExecuteQuery();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onClickHintFix = () => {
|
||||
const { hint, onClickHintFix } = this.props;
|
||||
if (onClickHintFix && hint && hint.fix) {
|
||||
onClickHintFix(hint.fix.action);
|
||||
const { hint, onExecuteHint } = this.props;
|
||||
if (onExecuteHint && hint && hint.fix) {
|
||||
onExecuteHint(hint.fix.action);
|
||||
}
|
||||
};
|
||||
|
||||
@ -242,7 +237,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
||||
};
|
||||
|
||||
render() {
|
||||
const { error, hint, initialQuery } = this.props;
|
||||
const { error, hint, query } = this.props;
|
||||
const { metricsOptions, syntaxLoaded } = this.state;
|
||||
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
|
||||
const chooserText = syntaxLoaded ? 'Metrics' : 'Loading metrics...';
|
||||
@ -261,10 +256,11 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
||||
<QueryField
|
||||
additionalPlugins={this.plugins}
|
||||
cleanText={cleanText}
|
||||
initialQuery={initialQuery.expr}
|
||||
initialQuery={query.expr}
|
||||
onTypeahead={this.onTypeahead}
|
||||
onWillApplySuggestion={willApplySuggestion}
|
||||
onValueChanged={this.onChangeQuery}
|
||||
onQueryChange={this.onChangeQuery}
|
||||
onExecuteQuery={this.props.onExecuteQuery}
|
||||
placeholder="Enter a PromQL query"
|
||||
portalOrigin="prometheus"
|
||||
syntaxLoaded={syntaxLoaded}
|
||||
|
@ -1,11 +1,8 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PromCheatSheet from './PromCheatSheet';
|
||||
import { ExploreStartPageProps } from '@grafana/ui';
|
||||
|
||||
interface Props {
|
||||
onClickExample: () => void;
|
||||
}
|
||||
|
||||
export default class PromStart extends PureComponent<Props> {
|
||||
export default class PromStart extends PureComponent<ExploreStartPageProps> {
|
||||
render() {
|
||||
return (
|
||||
<div className="grafana-info-box grafana-info-box--max-lg">
|
||||
|
@ -10,21 +10,21 @@ import { Alignments } from './Alignments';
|
||||
import { AlignmentPeriods } from './AlignmentPeriods';
|
||||
import { AliasBy } from './AliasBy';
|
||||
import { Help } from './Help';
|
||||
import { Target, MetricDescriptor } from '../types';
|
||||
import { StackdriverQuery, MetricDescriptor } from '../types';
|
||||
import { getAlignmentPickerData } from '../functions';
|
||||
import StackdriverDatasource from '../datasource';
|
||||
import { SelectOptionItem } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
onQueryChange: (target: Target) => void;
|
||||
onQueryChange: (target: StackdriverQuery) => void;
|
||||
onExecuteQuery: () => void;
|
||||
target: Target;
|
||||
target: StackdriverQuery;
|
||||
events: any;
|
||||
datasource: StackdriverDatasource;
|
||||
templateSrv: TemplateSrv;
|
||||
}
|
||||
|
||||
interface State extends Target {
|
||||
interface State extends StackdriverQuery {
|
||||
alignOptions: SelectOptionItem[];
|
||||
lastQuery: string;
|
||||
lastQueryError: string;
|
||||
|
@ -2,9 +2,10 @@ import { stackdriverUnitMappings } from './constants';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import _ from 'lodash';
|
||||
import StackdriverMetricFindQuery from './StackdriverMetricFindQuery';
|
||||
import { MetricDescriptor } from './types';
|
||||
import { StackdriverQuery, MetricDescriptor } from './types';
|
||||
import { DataSourceApi, DataQueryOptions } from '@grafana/ui/src/types';
|
||||
|
||||
export default class StackdriverDatasource {
|
||||
export default class StackdriverDatasource implements DataSourceApi<StackdriverQuery> {
|
||||
id: number;
|
||||
url: string;
|
||||
baseUrl: string;
|
||||
@ -39,9 +40,7 @@ export default class StackdriverDatasource {
|
||||
alignmentPeriod: this.templateSrv.replace(t.alignmentPeriod, options.scopedVars || {}),
|
||||
groupBys: this.interpolateGroupBys(t.groupBys, options.scopedVars),
|
||||
view: t.view || 'FULL',
|
||||
filters: (t.filters || []).map(f => {
|
||||
return this.templateSrv.replace(f, options.scopedVars || {});
|
||||
}),
|
||||
filters: this.interpolateFilters(t.filters, options.scopedVars),
|
||||
aliasBy: this.templateSrv.replace(t.aliasBy, options.scopedVars || {}),
|
||||
type: 'timeSeriesQuery',
|
||||
};
|
||||
@ -63,7 +62,13 @@ export default class StackdriverDatasource {
|
||||
}
|
||||
}
|
||||
|
||||
async getLabels(metricType, refId) {
|
||||
interpolateFilters(filters: string[], scopedVars: object) {
|
||||
return (filters || []).map(f => {
|
||||
return this.templateSrv.replace(f, scopedVars || {}, 'regex');
|
||||
});
|
||||
}
|
||||
|
||||
async getLabels(metricType: string, refId: string) {
|
||||
const response = await this.getTimeSeries({
|
||||
targets: [
|
||||
{
|
||||
@ -103,7 +108,7 @@ export default class StackdriverDatasource {
|
||||
return unit;
|
||||
}
|
||||
|
||||
async query(options) {
|
||||
async query(options: DataQueryOptions<StackdriverQuery>) {
|
||||
const result = [];
|
||||
const data = await this.getTimeSeries(options);
|
||||
if (data.results) {
|
||||
|
Before Width: | Height: | Size: 15 KiB |
@ -0,0 +1 @@
|
||||
<svg width="100" height="90" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M-1-1h102v92H-1z"/><g><path d="M151.637 29.785c-1.659.621-3.32 1.241-4.783 2.055-1.548-7.686-18.278-8.18-18.117 1.021.148 8.228 18.35 8.414 22.9 16.065 3.456 5.808 1.064 14.28-3.417 17.433-1.805 1.271-4.625 3.234-10.936 3.076-7.568-.19-13.65-5.277-16.065-12.305 1.474-1.151 3.464-1.777 5.468-2.393.087 9.334 18.304 12.687 20.509 3.418 3.661-15.375-24.686-9.097-24.267-25.636.375-14.998 25.388-16.197 28.708-2.734zM207.347 68.413h-5.466v-4.444c-2.872 2.517-5.263 5.222-10.254 5.467-10.316.51-17.038-10.377-10.256-17.773 4.38-4.774 13.169-5.41 20.512-2.05 1.548-10.171-13.626-11.842-16.407-4.44-1.698-.697-3.195-1.592-5.126-2.054 2.832-10.246 20.01-9.729 24.949-2.392 4.608 6.837.757 17.618 2.048 27.686zm-22.216-7.866c4.483 6.856 17.435 2.377 16.751-6.154-5.161-3.469-18.501-3.389-16.751 6.154zM416.873 53.029c-7.868.794-17.201.117-25.638.343-1.48 10.76 16.123 14.618 19.144 5.127 1.631.754 3.326 1.457 5.127 2.048-2.477 9.824-18.37 11.251-25.294 4.445-9.549-9.386-4.276-31.335 12.987-29.735 8.89.826 13.149 7.176 13.674 17.772zm-25.295-4.444h18.801c-.04-11.168-18.433-9.957-18.801 0zM347.486 36.283v32.13h-5.81v-32.13h5.81zM352.273 36.283h6.153c3.048 8.342 6.48 16.303 9.224 24.949 4.33-7.408 6.575-16.895 10.251-24.949h6.155c-4.39 10.646-8.865 21.217-12.988 32.13h-6.152c-3.907-11.019-8.635-21.217-12.643-32.13zM427.354 36.225h-5.525v32.111h5.982V48.885s1.845-9.322 11.396-7.021l2.417-5.867s-8.978-2.532-14.155 5.407l-.115-5.179zM322.434 36.225h-5.522v32.111h5.987V48.885s1.84-9.322 11.395-7.021l2.417-5.867s-8.976-2.532-14.159 5.407l-.118-5.179zM304.139 51.998c0 6.579-4.645 11.919-10.372 11.919-5.725 0-10.366-5.34-10.366-11.919 0-6.586 4.642-11.92 10.366-11.92 5.727 0 10.372 5.334 10.372 11.92zm-.107 11.916v4.19h5.742V21.038h-5.812v19.325c-2.789-3.472-6.805-5.649-11.269-5.649-8.424 0-15.253 7.768-15.253 17.341 0 9.576 6.829 17.344 15.253 17.344 4.496 0 8.536-2.21 11.33-5.724l.009.239z" fill="#6F6F6F"/><circle r="4.185" cy="25.306" cx="344.584" fill="#6F6F6F"/><path fill="#6F6F6F" d="M253.751 50.332l13.835-14.078h7.603l-12.337 12.711 13.21 19.321h-7.354l-10.346-14.959-4.738 4.861v10.225h-5.856V21.422h5.856v28.443zM236.855 46.471c-1.762-3.644-5.163-6.109-9.065-6.109-5.713 0-10.348 5.282-10.348 11.799 0 6.524 4.635 11.806 10.348 11.806 3.93 0 7.347-2.496 9.101-6.183l5.394 2.556c-2.779 5.419-8.227 9.097-14.497 9.097-9.083 0-16.451-7.733-16.451-17.275 0-9.537 7.368-17.269 16.451-17.269 6.247 0 11.683 3.653 14.467 9.041l-5.4 2.537zM160.884 26.693v9.747h-5.849v5.479h5.727v17.052s-.37 13.157 15.103 9.383l-1.947-4.995s-7.065 2.434-7.065-3.896V41.919h7.796V36.56h-7.674v-9.866h-6.091v-.001z"/><path fill="#009245" d="M50.794 41.715L27.708 84.812l46.207-.008z"/><path fill="#006837" d="M27.699 84.804L4.833 44.994h45.958z"/><path fill="#39B54A" d="M50.913 45.008H4.833L27.898 5.12H74.031z"/><path fill="#8CC63F" d="M74.031 5.12l23.236 39.84-23.352 39.844-23.002-39.796z"/></g></svg>
|
After Width: | Height: | Size: 2.9 KiB |
@ -14,8 +14,8 @@
|
||||
"description": "Google Stackdriver Datasource for Grafana",
|
||||
"version": "1.0.0",
|
||||
"logos": {
|
||||
"small": "img/stackdriver_logo.png",
|
||||
"large": "img/stackdriver_logo.png"
|
||||
"small": "img/stackdriver_logo.svg",
|
||||
"large": "img/stackdriver_logo.svg"
|
||||
},
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { QueryCtrl } from 'app/plugins/sdk';
|
||||
import { Target } from './types';
|
||||
import { StackdriverQuery } from './types';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
|
||||
export class StackdriverQueryCtrl extends QueryCtrl {
|
||||
@ -16,7 +16,7 @@ export class StackdriverQueryCtrl extends QueryCtrl {
|
||||
this.onExecuteQuery = this.onExecuteQuery.bind(this);
|
||||
}
|
||||
|
||||
onQueryChange(target: Target) {
|
||||
onQueryChange(target: StackdriverQuery) {
|
||||
Object.assign(this.target, target);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import StackdriverDataSource from '../datasource';
|
||||
import { metricDescriptors } from './testData';
|
||||
import moment from 'moment';
|
||||
import { TemplateSrvStub } from 'test/specs/helpers';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { CustomVariable } from 'app/features/templating/all';
|
||||
|
||||
describe('StackdriverDataSource', () => {
|
||||
const instanceSettings = {
|
||||
@ -9,7 +10,7 @@ describe('StackdriverDataSource', () => {
|
||||
defaultProject: 'testproject',
|
||||
},
|
||||
};
|
||||
const templateSrv = new TemplateSrvStub();
|
||||
const templateSrv = new TemplateSrv();
|
||||
const timeSrv = {};
|
||||
|
||||
describe('when performing testDataSource', () => {
|
||||
@ -154,15 +155,41 @@ describe('StackdriverDataSource', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when interpolating a template variable for the filter', () => {
|
||||
let interpolated;
|
||||
describe('and is single value variable', () => {
|
||||
beforeEach(() => {
|
||||
const filterTemplateSrv = initTemplateSrv('filtervalue1');
|
||||
const ds = new StackdriverDataSource(instanceSettings, {}, filterTemplateSrv, timeSrv);
|
||||
interpolated = ds.interpolateFilters(['resource.label.zone', '=~', '${test}'], {});
|
||||
});
|
||||
|
||||
it('should replace the variable with the value', () => {
|
||||
expect(interpolated.length).toBe(3);
|
||||
expect(interpolated[2]).toBe('filtervalue1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('and is multi value variable', () => {
|
||||
beforeEach(() => {
|
||||
const filterTemplateSrv = initTemplateSrv(['filtervalue1', 'filtervalue2'], true);
|
||||
const ds = new StackdriverDataSource(instanceSettings, {}, filterTemplateSrv, timeSrv);
|
||||
interpolated = ds.interpolateFilters(['resource.label.zone', '=~', '[[test]]'], {});
|
||||
});
|
||||
|
||||
it('should replace the variable with a regex expression', () => {
|
||||
expect(interpolated[2]).toBe('(filtervalue1|filtervalue2)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when interpolating a template variable for group bys', () => {
|
||||
let interpolated;
|
||||
|
||||
describe('and is single value variable', () => {
|
||||
beforeEach(() => {
|
||||
templateSrv.data = {
|
||||
test: 'groupby1',
|
||||
};
|
||||
const ds = new StackdriverDataSource(instanceSettings, {}, templateSrv, timeSrv);
|
||||
const groupByTemplateSrv = initTemplateSrv('groupby1');
|
||||
const ds = new StackdriverDataSource(instanceSettings, {}, groupByTemplateSrv, timeSrv);
|
||||
interpolated = ds.interpolateGroupBys(['[[test]]'], {});
|
||||
});
|
||||
|
||||
@ -174,10 +201,8 @@ describe('StackdriverDataSource', () => {
|
||||
|
||||
describe('and is multi value variable', () => {
|
||||
beforeEach(() => {
|
||||
templateSrv.data = {
|
||||
test: 'groupby1,groupby2',
|
||||
};
|
||||
const ds = new StackdriverDataSource(instanceSettings, {}, templateSrv, timeSrv);
|
||||
const groupByTemplateSrv = initTemplateSrv(['groupby1', 'groupby2'], true);
|
||||
const ds = new StackdriverDataSource(instanceSettings, {}, groupByTemplateSrv, timeSrv);
|
||||
interpolated = ds.interpolateGroupBys(['[[test]]'], {});
|
||||
});
|
||||
|
||||
@ -241,3 +266,19 @@ describe('StackdriverDataSource', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
function initTemplateSrv(values: any, multi = false) {
|
||||
const templateSrv = new TemplateSrv();
|
||||
templateSrv.init([
|
||||
new CustomVariable(
|
||||
{
|
||||
name: 'test',
|
||||
current: {
|
||||
value: values,
|
||||
},
|
||||
multi: multi,
|
||||
},
|
||||
{}
|
||||
),
|
||||
]);
|
||||
return templateSrv;
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { DataQuery } from '@grafana/ui/src/types';
|
||||
|
||||
export enum MetricFindQueryTypes {
|
||||
Services = 'services',
|
||||
MetricTypes = 'metricTypes',
|
||||
@ -20,20 +22,22 @@ export interface VariableQueryData {
|
||||
services: Array<{ value: string; name: string }>;
|
||||
}
|
||||
|
||||
export interface Target {
|
||||
defaultProject: string;
|
||||
unit: string;
|
||||
export interface StackdriverQuery extends DataQuery {
|
||||
defaultProject?: string;
|
||||
unit?: string;
|
||||
metricType: string;
|
||||
service: string;
|
||||
service?: string;
|
||||
refId: string;
|
||||
crossSeriesReducer: string;
|
||||
alignmentPeriod: string;
|
||||
alignmentPeriod?: string;
|
||||
perSeriesAligner: string;
|
||||
groupBys: string[];
|
||||
filters: string[];
|
||||
aliasBy: string;
|
||||
groupBys?: string[];
|
||||
filters?: string[];
|
||||
aliasBy?: string;
|
||||
metricKind: string;
|
||||
valueType: string;
|
||||
datasourceId?: number;
|
||||
view?: string;
|
||||
}
|
||||
|
||||
export interface AnnotationTarget {
|
||||
|
@ -274,6 +274,28 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
|
||||
if (popover.length > 0 && target.parents('.graph-legend').length === 0) {
|
||||
popover.hide();
|
||||
}
|
||||
|
||||
// hide time picker
|
||||
const timePickerDropDownIsOpen = elem.find('.gf-timepicker-dropdown').length > 0;
|
||||
if (timePickerDropDownIsOpen) {
|
||||
const targetIsInTimePickerDropDown = target.parents('.gf-timepicker-dropdown').length > 0;
|
||||
const targetIsInTimePickerNav = target.parents('.gf-timepicker-nav').length > 0;
|
||||
const targetIsDatePickerRowBtn = target.parents('td[id^="datepicker-"]').length > 0;
|
||||
const targetIsDatePickerHeaderBtn = target.parents('button[id^="datepicker-"]').length > 0;
|
||||
|
||||
if (
|
||||
targetIsInTimePickerNav ||
|
||||
targetIsInTimePickerDropDown ||
|
||||
targetIsDatePickerRowBtn ||
|
||||
targetIsDatePickerHeaderBtn
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.$apply(() => {
|
||||
scope.appEvent('closeTimepicker');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
// import { createLogger } from 'redux-logger';
|
||||
import { createLogger } from 'redux-logger';
|
||||
import sharedReducers from 'app/core/reducers';
|
||||
import alertingReducers from 'app/features/alerting/state/reducers';
|
||||
import teamsReducers from 'app/features/teams/state/reducers';
|
||||
@ -39,7 +39,7 @@ export function configureStore() {
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
// DEV builds we had the logger middleware
|
||||
setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk))));
|
||||
setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, createLogger()))));
|
||||
} else {
|
||||
setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk))));
|
||||
}
|
||||
|
@ -1,5 +1,14 @@
|
||||
import { ComponentClass } from 'react';
|
||||
import { Value } from 'slate';
|
||||
import { RawTimeRange, TimeRange, DataQuery, DataSourceSelectItem, DataSourceApi, QueryHint } from '@grafana/ui';
|
||||
import {
|
||||
RawTimeRange,
|
||||
TimeRange,
|
||||
DataQuery,
|
||||
DataSourceSelectItem,
|
||||
DataSourceApi,
|
||||
QueryHint,
|
||||
ExploreStartPageProps,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import { Emitter } from 'app/core/core';
|
||||
import { LogsModel } from 'app/core/logs_model';
|
||||
@ -102,7 +111,7 @@ export interface ExploreItemState {
|
||||
/**
|
||||
* React component to be shown when no queries have been run yet, e.g., for a query language cheat sheet.
|
||||
*/
|
||||
StartPage?: any;
|
||||
StartPage?: ComponentClass<ExploreStartPageProps>;
|
||||
/**
|
||||
* Width used for calculating the graph interval (can't have more datapoints than pixels)
|
||||
*/
|
||||
@ -144,10 +153,10 @@ export interface ExploreItemState {
|
||||
*/
|
||||
history: HistoryItem[];
|
||||
/**
|
||||
* Initial queries for this Explore, e.g., set via URL. Each query will be
|
||||
* converted to a query row. Query edits should be tracked in `modifiedQueries` though.
|
||||
* Queries for this Explore, e.g., set via URL. Each query will be
|
||||
* converted to a query row.
|
||||
*/
|
||||
initialQueries: DataQuery[];
|
||||
queries: DataQuery[];
|
||||
/**
|
||||
* True if this Explore area has been initialized.
|
||||
* Used to distinguish URL state injection versus split view state injection.
|
||||
@ -162,12 +171,6 @@ export interface ExploreItemState {
|
||||
* Log query result to be displayed in the logs result viewer.
|
||||
*/
|
||||
logsResult?: LogsModel;
|
||||
/**
|
||||
* Copy of `initialQueries` that tracks user edits.
|
||||
* Don't connect this property to a react component as it is updated on every query change.
|
||||
* Used when running queries. Needs to be reset to `initialQueries` when those are reset as well.
|
||||
*/
|
||||
modifiedQueries: DataQuery[];
|
||||
/**
|
||||
* Query intervals for graph queries to determine how many datapoints to return.
|
||||
* Needs to be updated when `datasourceInstance` or `containerWidth` is changed.
|
||||
@ -229,6 +232,11 @@ export interface ExploreItemState {
|
||||
* Table model that combines all query table results into a single table.
|
||||
*/
|
||||
tableResult?: TableModel;
|
||||
|
||||
/**
|
||||
* React keys for rendering of QueryRows
|
||||
*/
|
||||
queryKeys: string[];
|
||||
}
|
||||
|
||||
export interface ExploreUIState {
|
||||
|
@ -4,7 +4,7 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0A0A0C;}
|
||||
.st0{fill:#161719;}
|
||||
.st1{fill:#E3E2E2;}
|
||||
</style>
|
||||
<g>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
@ -5,7 +5,7 @@
|
||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
.st1{fill:#0A0A0C;}
|
||||
.st1{fill:#161719;}
|
||||
.st2{fill:url(#SVGID_2_);}
|
||||
.st3{fill:url(#SVGID_3_);}
|
||||
.st4{fill:url(#SVGID_4_);}
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@ -4,7 +4,7 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0A0A0C;}
|
||||
.st0{fill:#161719;}
|
||||
.st1{fill:#E3E2E2;}
|
||||
</style>
|
||||
<g>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@ -5,7 +5,7 @@
|
||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
.st1{fill:#0A0A0C;}
|
||||
.st1{fill:#161719;}
|
||||
.st2{fill:url(#SVGID_2_);}
|
||||
.st3{fill:url(#SVGID_3_);}
|
||||
</style>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@ -4,7 +4,7 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0A0A0C;}
|
||||
.st0{fill:#161719;}
|
||||
.st1{fill:#E3E2E2;}
|
||||
</style>
|
||||
<g>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@ -5,7 +5,7 @@
|
||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
.st1{fill:#0A0A0C;}
|
||||
.st1{fill:#161719;}
|
||||
.st2{fill:url(#SVGID_2_);}
|
||||
.st3{fill:url(#SVGID_3_);}
|
||||
.st4{fill:url(#SVGID_4_);}
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
@ -4,7 +4,7 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0A0A0C;}
|
||||
.st0{fill:#161719;}
|
||||
.st1{fill:#E3E2E2;}
|
||||
</style>
|
||||
<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@ -5,7 +5,7 @@
|
||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
.st1{fill:#0A0A0C;}
|
||||
.st1{fill:#161719;}
|
||||
.st2{fill:url(#SVGID_2_);}
|
||||
.st3{fill:url(#SVGID_3_);}
|
||||
</style>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@ -212,7 +212,7 @@
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.panel-editor-tabs {
|
||||
.panel-editor-tabs, .add-panel-widget__icon {
|
||||
.gicon-advanced-active {
|
||||
background-image: url('../img/icons_#{$theme-name}_theme/icon_advanced_active.svg');
|
||||
}
|
||||
|