Merge branch 'master' into fix/explore-do-not-clear-results
This commit is contained in:
@@ -147,9 +147,6 @@ jobs:
|
||||
- run:
|
||||
name: sha-sum packages
|
||||
command: 'go run build.go sha-dist'
|
||||
- run:
|
||||
name: Build Grafana.com master publisher
|
||||
command: 'go build -o scripts/publish scripts/build/publish.go'
|
||||
- run:
|
||||
name: Test and build Grafana.com release publisher
|
||||
command: 'cd scripts/build/release_publisher && go test . && go build -o release_publisher .'
|
||||
@@ -158,7 +155,6 @@ jobs:
|
||||
paths:
|
||||
- dist/grafana*
|
||||
- scripts/*.sh
|
||||
- scripts/publish
|
||||
- scripts/build/release_publisher/release_publisher
|
||||
- scripts/build/publish.sh
|
||||
|
||||
@@ -393,7 +389,7 @@ jobs:
|
||||
name: Publish to Grafana.com
|
||||
command: |
|
||||
rm dist/grafana-master-$(echo "${CIRCLE_SHA1}" | cut -b1-7).linux-x64.tar.gz
|
||||
./scripts/publish -apiKey ${GRAFANA_COM_API_KEY}
|
||||
cd dist && ../scripts/build/release_publisher/release_publisher -apikey ${GRAFANA_COM_API_KEY} -from-local
|
||||
|
||||
deploy-release:
|
||||
docker:
|
||||
|
||||
+1
-1
@@ -504,7 +504,7 @@ concurrent_render_limit = 5
|
||||
#################################### Explore #############################
|
||||
[explore]
|
||||
# Enable the Explore section
|
||||
enabled = false
|
||||
enabled = true
|
||||
|
||||
#################################### Internal Grafana Metrics ############
|
||||
# Metrics available at HTTP API Url /metrics
|
||||
|
||||
+1
-1
@@ -429,7 +429,7 @@ log_queries =
|
||||
#################################### Explore #############################
|
||||
[explore]
|
||||
# Enable the Explore section
|
||||
;enabled = false
|
||||
;enabled = true
|
||||
|
||||
#################################### Internal Grafana Metrics ##########################
|
||||
# Metrics available at HTTP API Url /metrics
|
||||
|
||||
@@ -336,7 +336,7 @@ func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) {
|
||||
"id": 123123,
|
||||
"gridPos": map[string]interface{}{
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"y": 3,
|
||||
"w": 24,
|
||||
"h": 4,
|
||||
},
|
||||
|
||||
@@ -718,7 +718,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
|
||||
AlertingNoDataOrNullValues = alerting.Key("nodata_or_nullvalues").MustString("no_data")
|
||||
|
||||
explore := iniFile.Section("explore")
|
||||
ExploreEnabled = explore.Key("enabled").MustBool(false)
|
||||
ExploreEnabled = explore.Key("enabled").MustBool(true)
|
||||
|
||||
panels := iniFile.Section("panels")
|
||||
cfg.EnableAlphaPanels = panels.Key("enable_alpha").MustBool(false)
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { LocationUpdate } from 'app/types';
|
||||
|
||||
export enum CoreActionTypes {
|
||||
UpdateLocation = 'UPDATE_LOCATION',
|
||||
}
|
||||
|
||||
export type Action = UpdateLocationAction;
|
||||
|
||||
export interface UpdateLocationAction {
|
||||
type: 'UPDATE_LOCATION';
|
||||
type: CoreActionTypes.UpdateLocation;
|
||||
payload: LocationUpdate;
|
||||
}
|
||||
|
||||
export const updateLocation = (location: LocationUpdate): UpdateLocationAction => ({
|
||||
type: 'UPDATE_LOCATION',
|
||||
type: CoreActionTypes.UpdateLocation,
|
||||
payload: location,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Action } from 'app/core/actions/location';
|
||||
import { Action, CoreActionTypes } from 'app/core/actions/location';
|
||||
import { LocationState } from 'app/types';
|
||||
import { renderUrl } from 'app/core/utils/url';
|
||||
import _ from 'lodash';
|
||||
@@ -12,7 +12,7 @@ export const initialState: LocationState = {
|
||||
|
||||
export const locationReducer = (state = initialState, action: Action): LocationState => {
|
||||
switch (action.type) {
|
||||
case 'UPDATE_LOCATION': {
|
||||
case CoreActionTypes.UpdateLocation: {
|
||||
const { path, routeParams } = action.payload;
|
||||
let query = action.payload.query || state.query;
|
||||
|
||||
@@ -24,9 +24,7 @@ export const locationReducer = (state = initialState, action: Action): LocationS
|
||||
return {
|
||||
url: renderUrl(path || state.path, query),
|
||||
path: path || state.path,
|
||||
query: {
|
||||
...query,
|
||||
},
|
||||
query: { ...query },
|
||||
routeParams: routeParams || state.routeParams,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -58,7 +58,6 @@ describe('when updating view state', () => {
|
||||
it('should remove params from query string', () => {
|
||||
viewState.update({ fullscreen: true, panelId: 1, edit: true });
|
||||
viewState.update({ fullscreen: false });
|
||||
expect(viewState.dashboard.meta.fullscreen).toBe(false);
|
||||
expect(viewState.state.fullscreen).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,7 +72,6 @@ export class DashboardViewStateSrv {
|
||||
}
|
||||
|
||||
_.extend(this.state, state);
|
||||
this.dashboard.meta.fullscreen = this.state.fullscreen;
|
||||
|
||||
if (!this.state.fullscreen) {
|
||||
this.state.fullscreen = null;
|
||||
@@ -117,10 +116,20 @@ export class DashboardViewStateSrv {
|
||||
}
|
||||
|
||||
syncState() {
|
||||
if (this.dashboard.meta.fullscreen) {
|
||||
if (this.state.fullscreen) {
|
||||
const panel = this.dashboard.getPanelById(this.state.panelId);
|
||||
|
||||
if (!panel) {
|
||||
this.state.fullscreen = null;
|
||||
this.state.panelId = null;
|
||||
this.state.edit = null;
|
||||
|
||||
this.update(this.state);
|
||||
|
||||
setTimeout(() => {
|
||||
appEvents.emit('alert-error', ['Error', 'Panel not found']);
|
||||
}, 100);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import { AutoSizer } from 'react-virtualized';
|
||||
import store from 'app/core/store';
|
||||
|
||||
// Components
|
||||
import { DataSourceSelectItem } from '@grafana/ui/src/types';
|
||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||
import { Alert } from './Error';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
import GraphContainer from './GraphContainer';
|
||||
@@ -21,18 +19,13 @@ import TimePicker, { parseTime } from './TimePicker';
|
||||
|
||||
// Actions
|
||||
import {
|
||||
changeDatasource,
|
||||
changeSize,
|
||||
changeTime,
|
||||
clearQueries,
|
||||
initializeExplore,
|
||||
modifyQueries,
|
||||
runQueries,
|
||||
scanStart,
|
||||
scanStop,
|
||||
setQueries,
|
||||
splitClose,
|
||||
splitOpen,
|
||||
} from './state/actions';
|
||||
|
||||
// Types
|
||||
@@ -41,27 +34,23 @@ import { ExploreItemState, ExploreUrlState, RangeScanner, ExploreId } from 'app/
|
||||
import { StoreState } from 'app/types';
|
||||
import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE } from 'app/core/utils/explore';
|
||||
import { Emitter } from 'app/core/utils/emitter';
|
||||
import { ExploreToolbar } from './ExploreToolbar';
|
||||
|
||||
interface ExploreProps {
|
||||
StartPage?: any;
|
||||
changeDatasource: typeof changeDatasource;
|
||||
changeSize: typeof changeSize;
|
||||
changeTime: typeof changeTime;
|
||||
clearQueries: typeof clearQueries;
|
||||
datasourceError: string;
|
||||
datasourceInstance: any;
|
||||
datasourceLoading: boolean | null;
|
||||
datasourceMissing: boolean;
|
||||
exploreDatasources: DataSourceSelectItem[];
|
||||
exploreId: ExploreId;
|
||||
initialDatasource?: string;
|
||||
initialQueries: DataQuery[];
|
||||
initializeExplore: typeof initializeExplore;
|
||||
initialized: boolean;
|
||||
loading: boolean;
|
||||
modifyQueries: typeof modifyQueries;
|
||||
range: RawTimeRange;
|
||||
runQueries: typeof runQueries;
|
||||
scanner?: RangeScanner;
|
||||
scanning?: boolean;
|
||||
scanRange?: RawTimeRange;
|
||||
@@ -69,8 +58,6 @@ interface ExploreProps {
|
||||
scanStop: typeof scanStop;
|
||||
setQueries: typeof setQueries;
|
||||
split: boolean;
|
||||
splitClose: typeof splitClose;
|
||||
splitOpen: typeof splitOpen;
|
||||
showingStartPage?: boolean;
|
||||
supportsGraph: boolean | null;
|
||||
supportsLogs: boolean | null;
|
||||
@@ -145,10 +132,6 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
||||
this.el = el;
|
||||
};
|
||||
|
||||
onChangeDatasource = async option => {
|
||||
this.props.changeDatasource(this.props.exploreId, option.value);
|
||||
};
|
||||
|
||||
onChangeTime = (range: TimeRange, changedByScanner?: boolean) => {
|
||||
if (this.props.scanning && !changedByScanner) {
|
||||
this.onStopScanning();
|
||||
@@ -156,23 +139,11 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
||||
this.props.changeTime(this.props.exploreId, range);
|
||||
};
|
||||
|
||||
onClickClear = () => {
|
||||
this.props.clearQueries(this.props.exploreId);
|
||||
};
|
||||
|
||||
onClickCloseSplit = () => {
|
||||
this.props.splitClose();
|
||||
};
|
||||
|
||||
// Use this in help pages to set page to a single query
|
||||
onClickExample = (query: DataQuery) => {
|
||||
this.props.setQueries(this.props.exploreId, [query]);
|
||||
};
|
||||
|
||||
onClickSplit = () => {
|
||||
this.props.splitOpen();
|
||||
};
|
||||
|
||||
onClickLabel = (key: string, value: string) => {
|
||||
this.onModifyQueries({ type: 'ADD_FILTER', key, value });
|
||||
};
|
||||
@@ -204,10 +175,6 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
||||
this.props.scanStop(this.props.exploreId);
|
||||
};
|
||||
|
||||
onSubmit = () => {
|
||||
this.props.runQueries(this.props.exploreId);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
StartPage,
|
||||
@@ -215,11 +182,8 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
||||
datasourceError,
|
||||
datasourceLoading,
|
||||
datasourceMissing,
|
||||
exploreDatasources,
|
||||
exploreId,
|
||||
loading,
|
||||
initialQueries,
|
||||
range,
|
||||
showingStartPage,
|
||||
split,
|
||||
supportsGraph,
|
||||
@@ -227,64 +191,10 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
||||
supportsTable,
|
||||
} = this.props;
|
||||
const exploreClass = split ? 'explore explore-split' : 'explore';
|
||||
const selectedDatasource = datasourceInstance
|
||||
? exploreDatasources.find(d => d.name === datasourceInstance.name)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div className={exploreClass} ref={this.getRef}>
|
||||
<div className="navbar">
|
||||
{exploreId === 'left' ? (
|
||||
<div>
|
||||
<a className="navbar-page-btn">
|
||||
<i className="fa fa-rocket" />
|
||||
Explore
|
||||
</a>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="navbar-page-btn" />
|
||||
<div className="navbar-buttons explore-first-button">
|
||||
<button className="btn navbar-button" onClick={this.onClickCloseSplit}>
|
||||
Close Split
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!datasourceMissing ? (
|
||||
<div className="navbar-buttons">
|
||||
<DataSourcePicker
|
||||
onChange={this.onChangeDatasource}
|
||||
datasources={exploreDatasources}
|
||||
current={selectedDatasource}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="navbar__spacer" />
|
||||
{exploreId === 'left' && !split ? (
|
||||
<div className="navbar-buttons">
|
||||
<button className="btn navbar-button" onClick={this.onClickSplit}>
|
||||
Split
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
<TimePicker ref={this.timepickerRef} range={range} onChangeTime={this.onChangeTime} />
|
||||
<div className="navbar-buttons">
|
||||
<button className="btn navbar-button navbar-button--no-icon" onClick={this.onClickClear}>
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
<div className="navbar-buttons relative">
|
||||
<button className="btn navbar-button navbar-button--primary" onClick={this.onSubmit}>
|
||||
Run Query{' '}
|
||||
{loading ? (
|
||||
<i className="fa fa-spinner fa-fw fa-spin run-icon" />
|
||||
) : (
|
||||
<i className="fa fa-level-down fa-fw run-icon" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ExploreToolbar exploreId={exploreId} timepickerRef={this.timepickerRef} onChangeTime={this.onChangeTime} />
|
||||
{datasourceLoading ? <div className="explore-container">Loading datasource...</div> : null}
|
||||
{datasourceMissing ? (
|
||||
<div className="explore-container">Please add a datasource that supports Explore (e.g., Prometheus).</div>
|
||||
@@ -341,30 +251,24 @@ function mapStateToProps(state: StoreState, { exploreId }) {
|
||||
datasourceInstance,
|
||||
datasourceLoading,
|
||||
datasourceMissing,
|
||||
exploreDatasources,
|
||||
initialDatasource,
|
||||
initialQueries,
|
||||
initialized,
|
||||
queryTransactions,
|
||||
range,
|
||||
showingStartPage,
|
||||
supportsGraph,
|
||||
supportsLogs,
|
||||
supportsTable,
|
||||
} = item;
|
||||
const loading = queryTransactions.some(qt => !qt.done);
|
||||
return {
|
||||
StartPage,
|
||||
datasourceError,
|
||||
datasourceInstance,
|
||||
datasourceLoading,
|
||||
datasourceMissing,
|
||||
exploreDatasources,
|
||||
initialDatasource,
|
||||
initialQueries,
|
||||
initialized,
|
||||
loading,
|
||||
queryTransactions,
|
||||
range,
|
||||
showingStartPage,
|
||||
split,
|
||||
@@ -375,18 +279,13 @@ function mapStateToProps(state: StoreState, { exploreId }) {
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
changeDatasource,
|
||||
changeSize,
|
||||
changeTime,
|
||||
clearQueries,
|
||||
initializeExplore,
|
||||
modifyQueries,
|
||||
runQueries,
|
||||
scanStart,
|
||||
scanStop,
|
||||
setQueries,
|
||||
splitClose,
|
||||
splitOpen,
|
||||
};
|
||||
|
||||
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(Explore));
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { hot } from 'react-hot-loader';
|
||||
|
||||
import { ExploreId } from 'app/types/explore';
|
||||
import { DataSourceSelectItem, RawTimeRange, TimeRange } from '@grafana/ui';
|
||||
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';
|
||||
|
||||
enum IconSide {
|
||||
left = 'left',
|
||||
right = 'right',
|
||||
}
|
||||
|
||||
const createResponsiveButton = (options: {
|
||||
splitted: boolean;
|
||||
title: string;
|
||||
onClick: () => void;
|
||||
buttonClassName?: string;
|
||||
iconClassName?: string;
|
||||
iconSide?: IconSide;
|
||||
}) => {
|
||||
const defaultOptions = {
|
||||
iconSide: IconSide.left,
|
||||
};
|
||||
const props = { ...options, defaultOptions };
|
||||
const { title, onClick, buttonClassName, iconClassName, splitted, iconSide } = props;
|
||||
|
||||
return (
|
||||
<button className={`btn navbar-button ${buttonClassName ? buttonClassName : ''}`} onClick={onClick}>
|
||||
{iconClassName && iconSide === IconSide.left ? <i className={`${iconClassName} icon-margin-right`} /> : null}
|
||||
<span className="btn-title">{!splitted ? title : ''}</span>
|
||||
{iconClassName && iconSide === IconSide.right ? <i className={`${iconClassName} icon-margin-left`} /> : null}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
interface OwnProps {
|
||||
exploreId: ExploreId;
|
||||
timepickerRef: React.RefObject<TimePicker>;
|
||||
onChangeTime: (range: TimeRange, changedByScanner?: boolean) => void;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
datasourceMissing: boolean;
|
||||
exploreDatasources: DataSourceSelectItem[];
|
||||
loading: boolean;
|
||||
range: RawTimeRange;
|
||||
selectedDatasource: DataSourceSelectItem;
|
||||
splitted: boolean;
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
changeDatasource: typeof changeDatasource;
|
||||
clearAll: typeof clearQueries;
|
||||
runQuery: typeof runQueries;
|
||||
closeSplit: typeof splitClose;
|
||||
split: typeof splitOpen;
|
||||
}
|
||||
|
||||
type Props = StateProps & DispatchProps & OwnProps;
|
||||
|
||||
export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
onChangeDatasource = async option => {
|
||||
this.props.changeDatasource(this.props.exploreId, option.value);
|
||||
};
|
||||
|
||||
onClearAll = () => {
|
||||
this.props.clearAll(this.props.exploreId);
|
||||
};
|
||||
|
||||
onRunQuery = () => {
|
||||
this.props.runQuery(this.props.exploreId);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
datasourceMissing,
|
||||
exploreDatasources,
|
||||
exploreId,
|
||||
loading,
|
||||
range,
|
||||
selectedDatasource,
|
||||
splitted,
|
||||
timepickerRef,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className={splitted ? 'explore-toolbar splitted' : 'explore-toolbar'}>
|
||||
<div className="explore-toolbar-item">
|
||||
<div className="explore-toolbar-header">
|
||||
<div className="explore-toolbar-header-title">
|
||||
{exploreId === 'left' && (
|
||||
<a className="navbar-page-btn">
|
||||
<i className="fa fa-rocket fa-fw" />
|
||||
Explore
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
<div className="explore-toolbar-header-close">
|
||||
{exploreId === 'right' && (
|
||||
<a onClick={this.props.closeSplit}>
|
||||
<i className="fa fa-times fa-fw" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="explore-toolbar-item">
|
||||
<div className="explore-toolbar-content">
|
||||
{!datasourceMissing ? (
|
||||
<div className="explore-toolbar-content-item">
|
||||
<div className="datasource-picker">
|
||||
<DataSourcePicker
|
||||
onChange={this.onChangeDatasource}
|
||||
datasources={exploreDatasources}
|
||||
current={selectedDatasource}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{exploreId === 'left' && !splitted ? (
|
||||
<div className="explore-toolbar-content-item">
|
||||
{createResponsiveButton({
|
||||
splitted,
|
||||
title: 'Split',
|
||||
onClick: this.props.split,
|
||||
iconClassName: 'fa fa-fw fa-columns icon-margin-right',
|
||||
iconSide: IconSide.left,
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="explore-toolbar-content-item timepicker">
|
||||
<TimePicker ref={timepickerRef} range={range} onChangeTime={this.props.onChangeTime} />
|
||||
</div>
|
||||
<div className="explore-toolbar-content-item">
|
||||
<button className="btn navbar-button navbar-button--no-icon" onClick={this.onClearAll}>
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
<div className="explore-toolbar-content-item">
|
||||
{createResponsiveButton({
|
||||
splitted,
|
||||
title: 'Run Query',
|
||||
onClick: this.onRunQuery,
|
||||
buttonClassName: 'navbar-button--primary',
|
||||
iconClassName: loading ? 'fa fa-spinner fa-fw fa-spin run-icon' : 'fa fa-level-down fa-fw run-icon',
|
||||
iconSide: IconSide.right,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps => {
|
||||
const splitted = state.explore.split;
|
||||
const exploreItem = state.explore[exploreId];
|
||||
const { datasourceInstance, datasourceMissing, exploreDatasources, queryTransactions, range } = exploreItem;
|
||||
const selectedDatasource = datasourceInstance
|
||||
? exploreDatasources.find(datasource => datasource.name === datasourceInstance.name)
|
||||
: undefined;
|
||||
const loading = queryTransactions.some(qt => !qt.done);
|
||||
|
||||
return {
|
||||
datasourceMissing,
|
||||
exploreDatasources,
|
||||
loading,
|
||||
range,
|
||||
selectedDatasource,
|
||||
splitted,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps: DispatchProps = {
|
||||
changeDatasource,
|
||||
clearAll: clearQueries,
|
||||
runQuery: runQueries,
|
||||
closeSplit: splitClose,
|
||||
split: splitOpen,
|
||||
};
|
||||
|
||||
export const ExploreToolbar = hot(module)(connect(mapStateToProps, mapDispatchToProps)(UnConnectedExploreToolbar));
|
||||
@@ -73,6 +73,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
placeholdersBuffer: PlaceholdersBuffer;
|
||||
plugins: any[];
|
||||
resetTimer: any;
|
||||
mounted: boolean;
|
||||
|
||||
constructor(props: QueryFieldProps, context) {
|
||||
super(props, context);
|
||||
@@ -93,10 +94,12 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
this.updateMenu();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
clearTimeout(this.resetTimer);
|
||||
}
|
||||
|
||||
@@ -347,13 +350,15 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
||||
};
|
||||
|
||||
resetTypeahead = () => {
|
||||
this.setState({
|
||||
suggestions: [],
|
||||
typeaheadIndex: 0,
|
||||
typeaheadPrefix: '',
|
||||
typeaheadContext: null,
|
||||
});
|
||||
this.resetTimer = null;
|
||||
if (this.mounted) {
|
||||
this.setState({
|
||||
suggestions: [],
|
||||
typeaheadIndex: 0,
|
||||
typeaheadPrefix: '',
|
||||
typeaheadContext: null,
|
||||
});
|
||||
this.resetTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
handleBlur = () => {
|
||||
|
||||
@@ -293,6 +293,7 @@ export default class TimePicker extends PureComponent<TimePickerProps, TimePicke
|
||||
|
||||
render() {
|
||||
const { isUtc, rangeString, refreshInterval } = this.state;
|
||||
|
||||
return (
|
||||
<div className="timepicker">
|
||||
<div className="navbar-buttons">
|
||||
|
||||
@@ -7,7 +7,7 @@ import { StoreState } from 'app/types';
|
||||
import { ExploreId, ExploreUrlState } from 'app/types/explore';
|
||||
import { parseUrlState } from 'app/core/utils/explore';
|
||||
|
||||
import { initializeExploreSplit } from './state/actions';
|
||||
import { initializeExploreSplit, resetExplore } from './state/actions';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
import Explore from './Explore';
|
||||
import { CustomScrollbar } from '@grafana/ui';
|
||||
@@ -16,6 +16,7 @@ interface WrapperProps {
|
||||
initializeExploreSplit: typeof initializeExploreSplit;
|
||||
split: boolean;
|
||||
updateLocation: typeof updateLocation;
|
||||
resetExplore: typeof resetExplore;
|
||||
urlStates: { [key: string]: string };
|
||||
}
|
||||
|
||||
@@ -42,6 +43,10 @@ export class Wrapper extends Component<WrapperProps> {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.resetExplore();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { split } = this.props;
|
||||
const { leftState, rightState } = this.urlStates;
|
||||
@@ -74,6 +79,7 @@ const mapStateToProps = (state: StoreState) => {
|
||||
const mapDispatchToProps = {
|
||||
initializeExploreSplit,
|
||||
updateLocation,
|
||||
resetExplore,
|
||||
};
|
||||
|
||||
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(Wrapper));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Types
|
||||
import { Emitter } from 'app/core/core';
|
||||
import { RawTimeRange, TimeRange, DataQuery, DataSourceSelectItem } from '@grafana/ui/src/types';
|
||||
import { RawTimeRange, TimeRange, DataQuery, DataSourceSelectItem } from '@grafana/ui/src/types';
|
||||
import {
|
||||
ExploreId,
|
||||
ExploreItemState,
|
||||
@@ -41,6 +41,7 @@ export enum ActionTypes {
|
||||
ToggleGraph = 'explore/TOGGLE_GRAPH',
|
||||
ToggleLogs = 'explore/TOGGLE_LOGS',
|
||||
ToggleTable = 'explore/TOGGLE_TABLE',
|
||||
ResetExplore = 'explore/RESET_EXPLORE',
|
||||
}
|
||||
|
||||
export interface AddQueryRowAction {
|
||||
@@ -270,6 +271,11 @@ export interface ToggleLogsAction {
|
||||
};
|
||||
}
|
||||
|
||||
export interface ResetExploreAction {
|
||||
type: ActionTypes.ResetExplore;
|
||||
payload: {};
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| AddQueryRowAction
|
||||
| ChangeQueryAction
|
||||
@@ -297,4 +303,5 @@ export type Action =
|
||||
| SplitOpenAction
|
||||
| ToggleGraphAction
|
||||
| ToggleLogsAction
|
||||
| ToggleTableAction;
|
||||
| ToggleTableAction
|
||||
| ResetExploreAction;
|
||||
|
||||
@@ -21,7 +21,7 @@ import { updateLocation } from 'app/core/actions';
|
||||
|
||||
// Types
|
||||
import { StoreState } from 'app/types';
|
||||
import { DataQuery, DataSourceSelectItem, QueryHint } from '@grafana/ui/src/types';
|
||||
import { DataQuery, DataSourceSelectItem, QueryHint } from '@grafana/ui/src/types';
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import {
|
||||
ExploreId,
|
||||
@@ -48,7 +48,6 @@ import {
|
||||
ScanStopAction,
|
||||
} from './actionTypes';
|
||||
|
||||
|
||||
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, ThunkableAction>;
|
||||
|
||||
/**
|
||||
@@ -539,6 +538,7 @@ export function runQueries(exploreId: ExploreId) {
|
||||
|
||||
if (!hasNonEmptyQuery(modifiedQueries)) {
|
||||
dispatch({ type: ActionTypes.RunQueriesEmpty, payload: { exploreId } });
|
||||
dispatch(stateSave()); // Remember to saves to state and update location
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -766,3 +766,12 @@ export function toggleTable(exploreId: ExploreId): ThunkResult<void> {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets state for explore.
|
||||
*/
|
||||
export function resetExplore(): ThunkResult<void> {
|
||||
return dispatch => {
|
||||
dispatch({ type: ActionTypes.ResetExplore, payload: {} });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -422,25 +422,19 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
||||
export const exploreReducer = (state = initialExploreState, action: Action): ExploreState => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.SplitClose: {
|
||||
return {
|
||||
...state,
|
||||
split: false,
|
||||
};
|
||||
return { ...state, split: false };
|
||||
}
|
||||
|
||||
case ActionTypes.SplitOpen: {
|
||||
return {
|
||||
...state,
|
||||
split: true,
|
||||
right: action.payload.itemState,
|
||||
};
|
||||
return { ...state, split: true, right: action.payload.itemState };
|
||||
}
|
||||
|
||||
case ActionTypes.InitializeExploreSplit: {
|
||||
return {
|
||||
...state,
|
||||
split: true,
|
||||
};
|
||||
return { ...state, split: true };
|
||||
}
|
||||
|
||||
case ActionTypes.ResetExplore: {
|
||||
return initialExploreState;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -173,8 +173,9 @@ export default class LokiLanguageProvider extends LanguageProvider {
|
||||
})
|
||||
);
|
||||
}
|
||||
// Return a cleaned LokiQuery
|
||||
return queries.map(query => ({
|
||||
...query,
|
||||
refId: query.refId,
|
||||
expr: '',
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -10,6 +10,23 @@
|
||||
"id": null,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"content": "<div class=\"text-center dashboard-header\">\n <span>Home Dashboard</span>\n</div>",
|
||||
"editable": true,
|
||||
"id": 1,
|
||||
"links": [],
|
||||
"mode": "html",
|
||||
"style": {},
|
||||
"title": "",
|
||||
"transparent": true,
|
||||
"type": "text",
|
||||
"gridPos": {
|
||||
"w": 24,
|
||||
"h": 3,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"folderId": 0,
|
||||
"headings": true,
|
||||
@@ -28,7 +45,7 @@
|
||||
"w": 12,
|
||||
"h": 17,
|
||||
"x": 0,
|
||||
"y": 1
|
||||
"y": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -43,7 +60,7 @@
|
||||
"w": 12,
|
||||
"h": 17,
|
||||
"x": 12,
|
||||
"y": 1
|
||||
"y": 6
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
+287
-143
@@ -1,180 +1,324 @@
|
||||
.explore {
|
||||
flex: 1 1 auto;
|
||||
.icon-margin-right {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
&-container {
|
||||
padding: $dashboard-padding;
|
||||
.icon-margin-left {
|
||||
margin-left: 0.25em;
|
||||
}
|
||||
|
||||
.run-icon {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.timepicker {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.timepicker-rangestring {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.datasource-picker {
|
||||
.ds-picker {
|
||||
min-width: 200px;
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.sidemenu-open {
|
||||
.explore-toolbar-header {
|
||||
padding: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
display: flex;
|
||||
|
||||
> .explore-split {
|
||||
width: 50%;
|
||||
.explore-toolbar-header-title {
|
||||
.navbar-page-btn {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Push split button a bit
|
||||
.explore-first-button {
|
||||
margin-left: 15px;
|
||||
.explore-toolbar {
|
||||
background: inherit;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: flex-start;
|
||||
height: auto;
|
||||
padding: 0px $dashboard-padding;
|
||||
border-bottom: 1px solid #0000;
|
||||
transition-duration: 0.35s;
|
||||
transition-timing-function: ease-in-out;
|
||||
transition-property: box-shadow, border-bottom;
|
||||
}
|
||||
|
||||
.explore-toolbar-item {
|
||||
position: relative;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.explore-toolbar.splitted {
|
||||
.explore-toolbar-item {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.explore-panel {
|
||||
margin-top: $panel-margin;
|
||||
.explore-toolbar-content-item:first-child {
|
||||
padding-left: 0;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.explore-panel__body {
|
||||
padding: $panel-padding;
|
||||
}
|
||||
.explore-toolbar-item:last-child {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.explore-panel__header {
|
||||
padding: $panel-padding;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 0;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
margin-bottom: 5px;
|
||||
transition: all 0.1s linear;
|
||||
}
|
||||
.explore-toolbar-header {
|
||||
display: flex;
|
||||
flex: 1 1 0;
|
||||
flex-flow: row nowrap;
|
||||
font-size: 18px;
|
||||
min-height: 55px;
|
||||
line-height: 55px;
|
||||
justify-content: space-between;
|
||||
margin-left: $panel-margin * 3;
|
||||
}
|
||||
|
||||
.explore-panel__header-label {
|
||||
font-weight: 500;
|
||||
margin-right: $panel-margin;
|
||||
font-size: $font-size-h6;
|
||||
box-shadow: $text-shadow-faint;
|
||||
}
|
||||
.explore-toolbar-header {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.explore-panel__header-buttons {
|
||||
margin-right: $panel-margin;
|
||||
font-size: $font-size-lg;
|
||||
line-height: $font-size-h6;
|
||||
}
|
||||
|
||||
// Make sure wrap buttons around on small screens
|
||||
.navbar {
|
||||
flex-wrap: wrap;
|
||||
height: auto;
|
||||
}
|
||||
.explore-toolbar-header-title {
|
||||
color: darken($link-color, 5%);
|
||||
|
||||
.navbar-page-btn {
|
||||
margin-right: 1rem;
|
||||
padding-left: $dashboard-padding;
|
||||
}
|
||||
|
||||
// Explore icon in header
|
||||
.fa {
|
||||
font-size: 100%;
|
||||
opacity: 0.75;
|
||||
margin-right: 0.5em;
|
||||
.fa {
|
||||
font-size: 100%;
|
||||
opacity: 0.75;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.explore-toolbar-header-close {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.explore-toolbar-content {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.explore-toolbar-content-item {
|
||||
padding: 10px 2px;
|
||||
}
|
||||
|
||||
.explore-toolbar-content-item:first-child {
|
||||
padding-left: $dashboard-padding;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1545px) {
|
||||
.explore-toolbar.splitted {
|
||||
.timepicker-rangestring {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle mode
|
||||
.navbar-button.active {
|
||||
color: $btn-active-text-color;
|
||||
background-color: $btn-active-bg;
|
||||
}
|
||||
|
||||
.navbar-button--no-icon {
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.result-options {
|
||||
margin: 2 * $panel-margin 0;
|
||||
}
|
||||
|
||||
.time-series-disclaimer {
|
||||
width: 300px;
|
||||
margin: $panel-margin auto;
|
||||
padding: 10px 0;
|
||||
border-radius: $border-radius;
|
||||
text-align: center;
|
||||
background-color: $panel-bg;
|
||||
|
||||
.disclaimer-icon {
|
||||
color: $yellow;
|
||||
margin-right: $panel-margin/2;
|
||||
}
|
||||
|
||||
.show-all-time-series {
|
||||
cursor: pointer;
|
||||
color: $external-link-color;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar .elapsed-time {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 3.5rem;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.graph-legend {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.explore-panel__loader {
|
||||
height: 2px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: none;
|
||||
margin: $panel-margin / 2;
|
||||
}
|
||||
|
||||
.explore-panel__loader--active:after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
width: 25%;
|
||||
top: 0;
|
||||
top: -50%;
|
||||
height: 250%;
|
||||
position: absolute;
|
||||
animation: loader 2s cubic-bezier(0.17, 0.67, 0.83, 0.67) 500ms;
|
||||
animation-iteration-count: 100;
|
||||
left: -25%;
|
||||
background: $blue;
|
||||
}
|
||||
|
||||
@keyframes loader {
|
||||
from {
|
||||
left: -25%;
|
||||
opacity: .1;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.datasource-picker {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1070px) {
|
||||
.timepicker {
|
||||
display: flex;
|
||||
|
||||
&-rangestring {
|
||||
margin-left: 0.5em;
|
||||
.timepicker-rangestring {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.run-icon {
|
||||
margin-left: 0.25em;
|
||||
transform: rotate(90deg);
|
||||
.explore-toolbar-content {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
.explore-toolbar.splitted {
|
||||
.explore-toolbar-content-item {
|
||||
padding: 2px 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
text-decoration: underline;
|
||||
.explore-toolbar-content-item {
|
||||
padding: 2px 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 803px) {
|
||||
.sidemenu-open {
|
||||
.explore-toolbar-header-title {
|
||||
.navbar-page-btn {
|
||||
padding-left: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.explore-toolbar-header-title {
|
||||
.navbar-page-btn {
|
||||
padding-left: 0;
|
||||
margin-left: $dashboard-padding;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-title {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 702px) {
|
||||
.explore-toolbar-content-item:first-child {
|
||||
padding-left: 2px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 544px) {
|
||||
.sidemenu-open {
|
||||
.explore-toolbar-header-title {
|
||||
.navbar-page-btn {
|
||||
padding-left: 0;
|
||||
margin-left: $dashboard-padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.explore-toolbar-header-title {
|
||||
.navbar-page-btn {
|
||||
padding-left: 0;
|
||||
margin-left: $dashboard-padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.explore {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.explore + .explore {
|
||||
border-left: 1px dotted $table-border;
|
||||
}
|
||||
|
||||
.explore-container {
|
||||
padding: $dashboard-padding;
|
||||
}
|
||||
|
||||
.explore-wrapper {
|
||||
display: flex;
|
||||
|
||||
> .explore-split {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.explore-panel {
|
||||
margin-top: $panel-margin;
|
||||
}
|
||||
|
||||
.explore-panel__body {
|
||||
padding: $panel-padding;
|
||||
}
|
||||
|
||||
.explore-panel__header {
|
||||
padding: $panel-padding;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 0;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
margin-bottom: 5px;
|
||||
transition: all 0.1s linear;
|
||||
}
|
||||
|
||||
.explore-panel__header-label {
|
||||
font-weight: 500;
|
||||
margin-right: $panel-margin;
|
||||
font-size: $font-size-h6;
|
||||
box-shadow: $text-shadow-faint;
|
||||
}
|
||||
|
||||
.explore-panel__header-buttons {
|
||||
margin-right: $panel-margin;
|
||||
font-size: $font-size-lg;
|
||||
line-height: $font-size-h6;
|
||||
}
|
||||
|
||||
.result-options {
|
||||
margin: 2 * $panel-margin 0;
|
||||
}
|
||||
|
||||
.time-series-disclaimer {
|
||||
width: 300px;
|
||||
margin: $panel-margin auto;
|
||||
padding: 10px 0;
|
||||
border-radius: $border-radius;
|
||||
text-align: center;
|
||||
background-color: $panel-bg;
|
||||
|
||||
.disclaimer-icon {
|
||||
color: $yellow;
|
||||
margin-right: $panel-margin/2;
|
||||
}
|
||||
|
||||
.show-all-time-series {
|
||||
cursor: pointer;
|
||||
color: $external-link-color;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar .elapsed-time {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 3.5rem;
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.graph-legend {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.explore-panel__loader {
|
||||
height: 2px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: none;
|
||||
margin: $panel-margin / 2;
|
||||
}
|
||||
|
||||
.explore-panel__loader--active:after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
width: 25%;
|
||||
top: 0;
|
||||
top: -50%;
|
||||
height: 250%;
|
||||
position: absolute;
|
||||
animation: loader 2s cubic-bezier(0.17, 0.67, 0.83, 0.67) 500ms;
|
||||
animation-iteration-count: 100;
|
||||
left: -25%;
|
||||
background: $blue;
|
||||
}
|
||||
|
||||
@keyframes loader {
|
||||
from {
|
||||
left: -25%;
|
||||
opacity: 0.1;
|
||||
}
|
||||
to {
|
||||
left: 100%;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.query-row {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var apiURL = flag.String("apiUrl", "https://grafana.com/api", "api url")
|
||||
var apiKey = flag.String("apiKey", "", "api key")
|
||||
var version = ""
|
||||
var versionRe = regexp.MustCompile(`grafana-(.*)(\.|_)(arm64|armhfp|aarch64|armv7|darwin|linux|windows|x86_64)`)
|
||||
var debVersionRe = regexp.MustCompile(`grafana_(.*)_(arm64|armv7|armhf|amd64)\.deb`)
|
||||
var builds = []build{}
|
||||
var architectureMapping = map[string]string{
|
||||
"armv7": "armv7",
|
||||
"armhfp": "armv7",
|
||||
"armhf": "armv7",
|
||||
"arm64": "arm64",
|
||||
"aarch64": "arm64",
|
||||
"amd64": "amd64",
|
||||
"x86_64": "amd64",
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *apiKey == "" {
|
||||
log.Fatalf("Require apiKey command line parameters")
|
||||
}
|
||||
|
||||
err := filepath.Walk("dist", packageWalker)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot find any packages to publish, %v", err)
|
||||
}
|
||||
|
||||
if version == "" {
|
||||
log.Fatalf("No version found")
|
||||
}
|
||||
|
||||
if len(builds) == 0 {
|
||||
log.Fatalf("No builds found")
|
||||
}
|
||||
|
||||
nightly := release{
|
||||
Version: version,
|
||||
ReleaseDate: time.Now(),
|
||||
Stable: false,
|
||||
Nightly: true,
|
||||
Beta: false,
|
||||
WhatsNewURL: "",
|
||||
ReleaseNotesURL: "",
|
||||
Builds: builds,
|
||||
}
|
||||
|
||||
postRequest("/grafana/versions", nightly, fmt.Sprintf("Create Release %s", nightly.Version))
|
||||
postRequest("/grafana/versions/"+nightly.Version, nightly, fmt.Sprintf("Update Release %s", nightly.Version))
|
||||
|
||||
for _, b := range nightly.Builds {
|
||||
postRequest(fmt.Sprintf("/grafana/versions/%s/packages", nightly.Version), b, fmt.Sprintf("Create Build %s %s", b.Os, b.Arch))
|
||||
postRequest(fmt.Sprintf("/grafana/versions/%s/packages/%s/%s", nightly.Version, b.Arch, b.Os), b, fmt.Sprintf("Update Build %s %s", b.Os, b.Arch))
|
||||
}
|
||||
}
|
||||
|
||||
func mapPackage(path string, name string, shaBytes []byte) (build, error) {
|
||||
log.Printf("Finding package file %s", name)
|
||||
result := versionRe.FindSubmatch([]byte(name))
|
||||
debResult := debVersionRe.FindSubmatch([]byte(name))
|
||||
|
||||
if len(result) > 0 {
|
||||
version = string(result[1])
|
||||
log.Printf("Version detected: %v", version)
|
||||
} else if len(debResult) > 0 {
|
||||
version = string(debResult[1])
|
||||
} else {
|
||||
return build{}, fmt.Errorf("Unable to figure out version from '%v'", name)
|
||||
}
|
||||
|
||||
os := ""
|
||||
if strings.Contains(name, "linux") {
|
||||
os = "linux"
|
||||
}
|
||||
if strings.HasSuffix(name, "windows-amd64.zip") {
|
||||
os = "win"
|
||||
}
|
||||
if strings.HasSuffix(name, "darwin-amd64.tar.gz") {
|
||||
os = "darwin"
|
||||
}
|
||||
if strings.HasSuffix(name, ".rpm") {
|
||||
os = "rhel"
|
||||
}
|
||||
if strings.HasSuffix(name, ".deb") {
|
||||
os = "deb"
|
||||
}
|
||||
if os == "" {
|
||||
return build{}, fmt.Errorf("Unable to figure out os from '%v'", name)
|
||||
}
|
||||
|
||||
arch := ""
|
||||
for archListed, archReal := range architectureMapping {
|
||||
if strings.Contains(name, archListed) {
|
||||
arch = archReal
|
||||
break
|
||||
}
|
||||
}
|
||||
if arch == "" {
|
||||
return build{}, fmt.Errorf("Unable to figure out arch from '%v'", name)
|
||||
}
|
||||
|
||||
return build{
|
||||
Os: os,
|
||||
Arch: arch,
|
||||
URL: "https://s3-us-west-2.amazonaws.com/grafana-releases/master/" + name,
|
||||
Sha256: string(shaBytes),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func packageWalker(path string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Printf("error: %v", err)
|
||||
}
|
||||
if f.Name() == "dist" || strings.Contains(f.Name(), "sha256") || strings.Contains(f.Name(), "latest") {
|
||||
return nil
|
||||
}
|
||||
|
||||
shaBytes, err := ioutil.ReadFile(path + ".sha256")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read sha256 file %v", err)
|
||||
}
|
||||
|
||||
build, err := mapPackage(path, f.Name(), shaBytes)
|
||||
if err != nil {
|
||||
log.Printf("Could not map metadata from package: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
builds = append(builds, build)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postRequest(url string, obj interface{}, desc string) {
|
||||
jsonBytes, _ := json.Marshal(obj)
|
||||
req, _ := http.NewRequest(http.MethodPost, (*apiURL)+url, bytes.NewReader(jsonBytes))
|
||||
req.Header.Add("Authorization", "Bearer "+(*apiKey))
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
if res.StatusCode == http.StatusOK {
|
||||
log.Printf("Action: %s \t OK", desc)
|
||||
} else {
|
||||
|
||||
if res.Body != nil {
|
||||
defer res.Body.Close()
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
if strings.Contains(string(body), "already exists") || strings.Contains(string(body), "Nothing to update") {
|
||||
log.Printf("Action: %s \t Already exists", desc)
|
||||
} else {
|
||||
log.Printf("Action: %s \t Failed - Status: %v", desc, res.Status)
|
||||
log.Printf("Resp: %s", body)
|
||||
log.Fatalf("Quitting")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type release struct {
|
||||
Version string `json:"version"`
|
||||
ReleaseDate time.Time `json:"releaseDate"`
|
||||
Stable bool `json:"stable"`
|
||||
Beta bool `json:"beta"`
|
||||
Nightly bool `json:"nightly"`
|
||||
WhatsNewURL string `json:"whatsNewUrl"`
|
||||
ReleaseNotesURL string `json:"releaseNotesUrl"`
|
||||
Builds []build `json:"-"`
|
||||
}
|
||||
|
||||
type build struct {
|
||||
Os string `json:"os"`
|
||||
URL string `json:"url"`
|
||||
Sha256 string `json:"sha256"`
|
||||
Arch string `json:"arch"`
|
||||
}
|
||||
@@ -127,11 +127,21 @@ var completeBuildArtifactConfigurations = []buildArtifact{
|
||||
arch: "armv7",
|
||||
urlPostfix: "_armhf.deb",
|
||||
},
|
||||
{
|
||||
os: "deb",
|
||||
arch: "armv6",
|
||||
urlPostfix: "_armel.deb",
|
||||
},
|
||||
{
|
||||
os: "rhel",
|
||||
arch: "armv7",
|
||||
urlPostfix: ".armhfp.rpm",
|
||||
},
|
||||
{
|
||||
os: "linux",
|
||||
arch: "armv6",
|
||||
urlPostfix: ".linux-armv6.tar.gz",
|
||||
},
|
||||
{
|
||||
os: "linux",
|
||||
arch: "armv7",
|
||||
|
||||
Reference in New Issue
Block a user