mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
wip: convert angular directives to react components
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
// import { OptionPicker } from './OptionPicker';
|
||||
import { OptionGroupPicker } from './OptionGroupPicker';
|
||||
// import { alignmentPeriods } from '../constants';
|
||||
// import { getAlignmentOptionsByMetric, getAggregationOptionsByMetric } from '../functions';
|
||||
import { getAggregationOptionsByMetric } from '../functions';
|
||||
// import kbn from 'app/core/utils/kbn';
|
||||
|
||||
export interface Props {
|
||||
onChange: (metricDescriptor) => void;
|
||||
templateSrv: any;
|
||||
valueType: string;
|
||||
metricKind: string;
|
||||
aggregation: {
|
||||
crossSeriesReducer: string;
|
||||
alignmentPeriod: string;
|
||||
perSeriesAligner: string;
|
||||
groupBys: string[];
|
||||
};
|
||||
}
|
||||
|
||||
interface State {
|
||||
alignmentPeriods: any[];
|
||||
alignOptions: any[];
|
||||
aggOptions: any[];
|
||||
}
|
||||
|
||||
export class AggregationPicker extends React.Component<Props, State> {
|
||||
state: State = {
|
||||
alignmentPeriods: [],
|
||||
alignOptions: [],
|
||||
aggOptions: [],
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setAggOptions();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
const { valueType, metricKind, aggregation } = this.props;
|
||||
if (
|
||||
nextProps.valueType !== valueType ||
|
||||
nextProps.metricKind !== metricKind ||
|
||||
nextProps.aggregation.groupBys !== aggregation.groupBys
|
||||
) {
|
||||
this.setAggOptions();
|
||||
}
|
||||
}
|
||||
|
||||
setAggOptions() {
|
||||
const { valueType, metricKind, aggregation, templateSrv } = this.props;
|
||||
let aggregations = getAggregationOptionsByMetric(valueType, metricKind).map(a => ({
|
||||
...a,
|
||||
label: a.text,
|
||||
}));
|
||||
if (!aggregations.find(o => o.value === templateSrv.replace(aggregation.crossSeriesReducer))) {
|
||||
this.deselectAggregationOption('REDUCE_NONE');
|
||||
}
|
||||
|
||||
if (aggregation.groupBys.length > 0) {
|
||||
aggregations = aggregations.filter(o => o.value !== 'REDUCE_NONE');
|
||||
this.deselectAggregationOption('REDUCE_NONE');
|
||||
}
|
||||
this.setState({
|
||||
aggOptions: [
|
||||
this.getTemplateVariablesGroup(),
|
||||
{
|
||||
label: 'Aggregations',
|
||||
options: aggregations,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
deselectAggregationOption(notValidOptionValue: string) {
|
||||
const aggregations = getAggregationOptionsByMetric(this.props.valueType, this.props.metricKind);
|
||||
const newValue = aggregations.find(o => o.value !== notValidOptionValue);
|
||||
this.handleAggregationChange(newValue ? newValue.value : '');
|
||||
}
|
||||
|
||||
handleAggregationChange(value) {
|
||||
this.props.onChange(value);
|
||||
// this.$scope.refresh();
|
||||
}
|
||||
|
||||
getTemplateVariablesGroup() {
|
||||
return {
|
||||
label: 'Template Variables',
|
||||
options: this.props.templateSrv.variables.map(v => ({
|
||||
label: `$${v.name}`,
|
||||
value: `$${v.name}`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { aggOptions } = this.state;
|
||||
const { aggregation } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<label className="gf-form-label query-keyword width-9">Aggregation</label>
|
||||
<div className="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
|
||||
<OptionGroupPicker
|
||||
onChange={value => this.handleAggregationChange(value)}
|
||||
selected={aggregation.crossSeriesReducer}
|
||||
groups={aggOptions}
|
||||
searchable={true}
|
||||
placeholder="Select Aggregation"
|
||||
className="width-15"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<label className="gf-form-label gf-form-label--grow">
|
||||
<a ng-click="ctrl.target.showAggregationOptions = !ctrl.target.showAggregationOptions">
|
||||
<i className="fa fa-caret-down" ng-show="ctrl.target.showAggregationOptions" />
|
||||
<i className="fa fa-caret-right" ng-hide="ctrl.target.showAggregationOptions" /> Advanced Options
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
239
public/app/plugins/datasource/stackdriver/components/Filter.tsx
Normal file
239
public/app/plugins/datasource/stackdriver/components/Filter.tsx
Normal file
@@ -0,0 +1,239 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Segment from './Segment';
|
||||
import { QueryMeta, Target } from '../types';
|
||||
import { FilterSegments } from '../filter_segments';
|
||||
|
||||
export interface Props {
|
||||
onChange: (metricDescriptor) => void;
|
||||
templateSrv: any;
|
||||
labelData: QueryMeta;
|
||||
loading: Promise<any>;
|
||||
target: Target;
|
||||
uiSegmentSrv: any;
|
||||
}
|
||||
|
||||
interface State {
|
||||
defaultRemoveGroupByValue: string;
|
||||
resourceTypeValue: string;
|
||||
groupBySegments: any[];
|
||||
// filterSegments: FilterSegments;
|
||||
filterSegments: any;
|
||||
removeSegment?: any;
|
||||
}
|
||||
|
||||
export class Filter extends React.Component<Props, State> {
|
||||
state: State = {
|
||||
defaultRemoveGroupByValue: '-- remove group by --',
|
||||
resourceTypeValue: 'resource.type',
|
||||
groupBySegments: [],
|
||||
filterSegments: new FilterSegments(this.getFilterKeys.bind(this), this.getFilterValues.bind(this)),
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.initSegments(false);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return this.state.filterSegments.filterSegments.length > 0;
|
||||
}
|
||||
|
||||
initSegments(hideGroupBys: boolean) {
|
||||
this.state.filterSegments.init(this.props.uiSegmentSrv);
|
||||
if (!hideGroupBys) {
|
||||
this.setState({
|
||||
groupBySegments: this.props.target.aggregation.groupBys.map(groupBy => {
|
||||
return this.props.uiSegmentSrv.getSegmentForValue(groupBy);
|
||||
}),
|
||||
});
|
||||
|
||||
this.ensurePlusButton(this.state.groupBySegments);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
removeSegment: this.props.uiSegmentSrv.newSegment({ fake: true, value: '-- remove group by --' }),
|
||||
});
|
||||
|
||||
this.state.filterSegments.buildSegmentModel(this.props.target.filters);
|
||||
}
|
||||
|
||||
async createLabelKeyElements() {
|
||||
await this.props.loading;
|
||||
|
||||
let elements = Object.keys(this.props.labelData.metricLabels || {}).map(l => {
|
||||
return this.props.uiSegmentSrv.newSegment({
|
||||
value: `metric.label.${l}`,
|
||||
expandable: false,
|
||||
});
|
||||
});
|
||||
|
||||
elements = [
|
||||
...elements,
|
||||
...Object.keys(this.props.labelData.resourceLabels || {}).map(l => {
|
||||
return this.props.uiSegmentSrv.newSegment({
|
||||
value: `resource.label.${l}`,
|
||||
expandable: false,
|
||||
});
|
||||
}),
|
||||
];
|
||||
|
||||
if (this.props.labelData.resourceTypes && this.props.labelData.resourceTypes.length > 0) {
|
||||
elements = [
|
||||
...elements,
|
||||
this.props.uiSegmentSrv.newSegment({
|
||||
value: this.state.resourceTypeValue,
|
||||
expandable: false,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
async getFilterKeys(segment, removeText?: string) {
|
||||
let elements = await this.createLabelKeyElements();
|
||||
|
||||
if (this.props.target.filters.indexOf(this.state.resourceTypeValue) !== -1) {
|
||||
elements = elements.filter(e => e.value !== this.state.resourceTypeValue);
|
||||
}
|
||||
|
||||
const noValueOrPlusButton = !segment || segment.type === 'plus-button';
|
||||
if (noValueOrPlusButton && elements.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
...elements,
|
||||
this.props.uiSegmentSrv.newSegment({ fake: true, value: removeText || this.state.defaultRemoveGroupByValue }),
|
||||
];
|
||||
}
|
||||
|
||||
async getGroupBys(segment) {
|
||||
let elements = await this.createLabelKeyElements();
|
||||
|
||||
elements = elements.filter(e => this.props.target.aggregation.groupBys.indexOf(e.value) === -1);
|
||||
const noValueOrPlusButton = !segment || segment.type === 'plus-button';
|
||||
if (noValueOrPlusButton && elements.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
this.state.removeSegment.value = this.state.defaultRemoveGroupByValue;
|
||||
return [...elements, this.state.removeSegment];
|
||||
}
|
||||
|
||||
groupByChanged(segment, index) {
|
||||
if (segment.value === this.state.removeSegment.value) {
|
||||
// this.groupBySegments.splice(index, 1);
|
||||
} else {
|
||||
segment.type = 'value';
|
||||
}
|
||||
|
||||
const reducer = (memo, seg) => {
|
||||
if (!seg.fake) {
|
||||
memo.push(seg.value);
|
||||
}
|
||||
return memo;
|
||||
};
|
||||
|
||||
this.props.target.aggregation.groupBys = this.state.groupBySegments.reduce(reducer, []);
|
||||
this.ensurePlusButton(this.state.groupBySegments);
|
||||
// this.$rootScope.$broadcast('metricTypeChanged');
|
||||
// this.$scope.refresh();
|
||||
}
|
||||
|
||||
async getFilters(segment, index) {
|
||||
await this.props.loading;
|
||||
const hasNoFilterKeys =
|
||||
this.props.labelData.metricLabels && Object.keys(this.props.labelData.metricLabels).length === 0;
|
||||
return this.state.filterSegments.getFilters(segment, index, hasNoFilterKeys);
|
||||
}
|
||||
|
||||
getFilterValues(index) {
|
||||
const filterKey = this.props.templateSrv.replace(this.state.filterSegments.filterSegments[index - 2].value);
|
||||
if (
|
||||
!filterKey ||
|
||||
!this.props.labelData.metricLabels ||
|
||||
Object.keys(this.props.labelData.metricLabels).length === 0
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const shortKey = filterKey.substring(filterKey.indexOf('.label.') + 7);
|
||||
|
||||
if (filterKey.startsWith('metric.label.') && this.props.labelData.metricLabels.hasOwnProperty(shortKey)) {
|
||||
return this.props.labelData.metricLabels[shortKey];
|
||||
}
|
||||
|
||||
if (filterKey.startsWith('resource.label.') && this.props.labelData.resourceLabels.hasOwnProperty(shortKey)) {
|
||||
return this.props.labelData.resourceLabels[shortKey];
|
||||
}
|
||||
|
||||
if (filterKey === this.state.resourceTypeValue) {
|
||||
return this.props.labelData.resourceTypes;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
filterSegmentUpdated(segment, index) {
|
||||
this.props.target.filters = this.state.filterSegments.filterSegmentUpdated(segment, index);
|
||||
// this.$scope.refresh();
|
||||
}
|
||||
|
||||
ensurePlusButton(segments) {
|
||||
const count = segments.length;
|
||||
const lastSegment = segments[Math.max(count - 1, 0)];
|
||||
|
||||
if (!lastSegment || lastSegment.type !== 'plus-button') {
|
||||
segments.push(this.props.uiSegmentSrv.newPlusButton());
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { filterSegments } = this.state;
|
||||
// const { metrifilterSegmentscType } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label query-keyword width-9">Filter</span>
|
||||
<div className="gf-form">
|
||||
{filterSegments.filterSegments.map((segment, i) => (
|
||||
<Segment
|
||||
key={i}
|
||||
segment={segment}
|
||||
getOptions={() => this.getFilters(segment, i)}
|
||||
onChange={segment => this.filterSegmentUpdated(segment, i)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label gf-form-label--grow" />
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="gf-form-inline" ng-hide="ctrl.$scope.hideGroupBys">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label query-keyword width-9">Group By</span>
|
||||
<div className="gf-form" ng-repeat="segment in ctrl.groupBySegments">
|
||||
<Segment
|
||||
segment="segment"
|
||||
get-options="ctrl.getGroupBys(segment)"
|
||||
on-change="ctrl.groupByChanged(segment, $index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label gf-form-label--grow" />
|
||||
</div>
|
||||
</div> */}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import appEvents from 'app/core/app_events';
|
||||
|
||||
import { MetricPicker } from './MetricPicker';
|
||||
import { Filter } from './Filter';
|
||||
// import { AggregationPicker } from './AggregationPicker';
|
||||
import { Target, QueryMeta } from '../types';
|
||||
|
||||
export interface Props {
|
||||
onChange: (target: Target) => void;
|
||||
target: Target;
|
||||
datasource: any;
|
||||
templateSrv: any;
|
||||
uiSegmentSrv: any;
|
||||
}
|
||||
|
||||
interface State {
|
||||
target: Target;
|
||||
labelData: QueryMeta;
|
||||
loadLabelsPromise: Promise<any>;
|
||||
}
|
||||
|
||||
const DefaultTarget: Target = {
|
||||
defaultProject: 'loading project...',
|
||||
metricType: '',
|
||||
refId: '',
|
||||
service: '',
|
||||
unit: '',
|
||||
aggregation: {
|
||||
crossSeriesReducer: 'REDUCE_MEAN',
|
||||
alignmentPeriod: 'stackdriver-auto',
|
||||
perSeriesAligner: 'ALIGN_MEAN',
|
||||
groupBys: [],
|
||||
},
|
||||
filters: [],
|
||||
aliasBy: '',
|
||||
metricKind: '',
|
||||
valueType: '',
|
||||
};
|
||||
|
||||
export class QueryEditor extends React.Component<Props, State> {
|
||||
state: State = { labelData: null, loadLabelsPromise: null, target: DefaultTarget };
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleMetricTypeChange = this.handleMetricTypeChange.bind(this);
|
||||
this.handleAggregationChange = this.handleAggregationChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({ target: this.props.target });
|
||||
this.getLabels();
|
||||
}
|
||||
|
||||
async getLabels() {
|
||||
const loadLabelsPromise = new Promise(async resolve => {
|
||||
try {
|
||||
const { meta } = await this.props.datasource.getLabels(this.props.target.metricType, this.props.target.refId);
|
||||
this.setState({ labelData: meta });
|
||||
resolve();
|
||||
} catch (error) {
|
||||
appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.props.target.metricType]);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
this.setState({ loadLabelsPromise });
|
||||
}
|
||||
|
||||
handleMetricTypeChange({ valueType, metricKind, type, unit }) {
|
||||
this.setState({
|
||||
target: {
|
||||
...this.state.target,
|
||||
...{
|
||||
metricType: type,
|
||||
unit,
|
||||
valueType,
|
||||
metricKind,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// this.$rootScope.$broadcast('metricTypeChanged');
|
||||
// this.getLabels();
|
||||
// this.refresh();
|
||||
}
|
||||
|
||||
handleAggregationChange(crossSeriesReducer) {
|
||||
// this.target.aggregation.crossSeriesReducer = crossSeriesReducer;
|
||||
// this.refresh();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { labelData, loadLabelsPromise, target } = this.state;
|
||||
const { defaultProject, metricType } = target;
|
||||
const { templateSrv, datasource, uiSegmentSrv } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MetricPicker
|
||||
defaultProject={defaultProject}
|
||||
metricType={metricType}
|
||||
templateSrv={templateSrv}
|
||||
datasource={datasource}
|
||||
onChange={value => this.handleMetricTypeChange(value)}
|
||||
/>
|
||||
<Filter
|
||||
onChange={() => console.log('change filter')}
|
||||
target={target}
|
||||
uiSegmentSrv={uiSegmentSrv}
|
||||
labelData={labelData}
|
||||
templateSrv={templateSrv}
|
||||
loading={loadLabelsPromise}
|
||||
/>
|
||||
{/* target="ctrl.target" refresh="ctrl.refresh()" loading="ctrl.loadLabelsPromise" label-data="ctrl.labelData" */}
|
||||
{/* <stackdriver-filter
|
||||
target="target"
|
||||
refresh="target.refresh()"
|
||||
loading="target.loadLabelsPromise"
|
||||
label-data="target.labelData"
|
||||
/>
|
||||
<aggregation-picker
|
||||
value-type="target.target.valueType"
|
||||
metric-kind="target.target.metricKind"
|
||||
aggregation="target.target.aggregation"
|
||||
alignment-period="target.lastQueryMeta.alignmentPeriod"
|
||||
refresh="target.refresh()"
|
||||
template-srv="target.templateSrv"
|
||||
datasource="target.datasource"
|
||||
on-change="target.handleAggregationChange"
|
||||
/>
|
||||
|
||||
<stackdriver-aggregation
|
||||
target="target.target"
|
||||
alignment-period="target.lastQueryMeta.alignmentPeriod"
|
||||
refresh="target.refresh()"
|
||||
/> */}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
|
||||
import 'app/core/directives/metric_segment';
|
||||
|
||||
interface QueryEditorProps {
|
||||
segment: any;
|
||||
getOptions: () => Promise<any[]>;
|
||||
onChange: (segment, index) => void;
|
||||
key: number;
|
||||
}
|
||||
|
||||
export default class Segment extends PureComponent<QueryEditorProps, any> {
|
||||
element: any;
|
||||
component: AngularComponent;
|
||||
|
||||
async componentDidMount() {
|
||||
if (!this.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { segment, getOptions, onChange } = this.props;
|
||||
const loader = getAngularLoader();
|
||||
const template = '<metric-segment> </metric-segment>';
|
||||
|
||||
const scopeProps = {
|
||||
segment,
|
||||
onChange,
|
||||
getOptions,
|
||||
debounce: false,
|
||||
};
|
||||
|
||||
this.component = loader.load(this.element, scopeProps, template);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.component) {
|
||||
this.component.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div ref={element => (this.element = element)} style={{ width: '100%' }} />;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user