grafana/public/app/plugins/datasource/graphite/query_ctrl.ts
Piotr Jamróz dbe7d3298d
Graphite: Migrate to React (part 1: move state to redux) (#36484)
* Add UMLs

* Add rendered diagrams

* Move QueryCtrl to flux

* Remove redundant param in the reducer

* Use named imports for lodash and fix typing for GraphiteTagOperator

* Add missing async/await

* Extract providers to a separate file

* Clean up async await

* Rename controller functions back to main

* Simplify creating actions

* Re-order controller functions

* Separate helpers from actions

* Rename vars

* Simplify helpers

* Move controller methods to state reducers

* Remove docs (they are added in design doc)

* Move actions.ts to state folder

* Add docs

* Add old methods stubs for easier review

* Check how state dependencies will be mapped

* Rename state to store

* Rename state to store

* Rewrite spec tests for Graphite Query Controller

* Update docs

* Update docs

* Update public/app/plugins/datasource/graphite/state/helpers.ts

Co-authored-by: Giordano Ricci <me@giordanoricci.com>

* Update public/app/plugins/datasource/graphite/state/helpers.ts

Co-authored-by: Giordano Ricci <me@giordanoricci.com>

* Update public/app/plugins/datasource/graphite/state/helpers.ts

Co-authored-by: Giordano Ricci <me@giordanoricci.com>

* Update public/app/plugins/datasource/graphite/state/providers.ts

Co-authored-by: Giordano Ricci <me@giordanoricci.com>

* Update public/app/plugins/datasource/graphite/state/providers.ts

Co-authored-by: Giordano Ricci <me@giordanoricci.com>

* Update public/app/plugins/datasource/graphite/state/providers.ts

Co-authored-by: Giordano Ricci <me@giordanoricci.com>

* Update public/app/plugins/datasource/graphite/state/providers.ts

Co-authored-by: Giordano Ricci <me@giordanoricci.com>

* Update public/app/plugins/datasource/graphite/state/providers.ts

Co-authored-by: Giordano Ricci <me@giordanoricci.com>

* Update public/app/plugins/datasource/graphite/state/providers.ts

Co-authored-by: Giordano Ricci <me@giordanoricci.com>

* Add more type definitions

* Load function definitions before parsing the target on initial load

* Change targetChanged to updateQuery to avoid mutating state directly

It's also needed for extra refresh/runQuery execution as handleTargetChanged doesn't handle changing the raw query

* Fix updating query after adding a function

* Simplify updating function params

* Remove redundant awaits

* Use redux Action

* Use more specific type for GraphiteTag

Co-authored-by: Giordano Ricci <me@giordanoricci.com>
2021-07-21 09:24:36 +02:00

260 lines
8.0 KiB
TypeScript

import './add_graphite_func';
import './func_editor';
import GraphiteQuery from './graphite_query';
import { QueryCtrl } from 'app/plugins/sdk';
import { auto } from 'angular';
import { TemplateSrv } from '@grafana/runtime';
import { actions } from './state/actions';
import { getAltSegments, getTagOperators, getTags, getTagsAsSegments, getTagValues } from './state/providers';
import { createStore, GraphiteQueryEditorState } from './state/store';
import {
AngularDropdownOptions,
GraphiteActionDispatcher,
GraphiteQueryEditorAngularDependencies,
GraphiteSegment,
GraphiteTag,
} from './types';
import { ChangeEvent } from 'react';
/**
* @deprecated Moved to state/store
*
* Note: methods marked with WIP are kept for easier diffing with previous changes. They will be removed when
* GraphiteQueryCtrl is replaced with a react component.
*/
export class GraphiteQueryCtrl extends QueryCtrl {
static templateUrl = 'partials/query.editor.html';
queryModel: GraphiteQuery;
segments: any[] = [];
addTagSegments: any[] = [];
removeTagValue: string;
supportsTags = false;
paused = false;
private state: GraphiteQueryEditorState;
private readonly dispatch: GraphiteActionDispatcher;
/** @ngInject */
constructor(
$scope: any,
$injector: auto.IInjectorService,
private uiSegmentSrv: any,
private templateSrv: TemplateSrv
) {
super($scope, $injector);
// This controller will be removed once it's root partial (query.editor.html) renders only React components.
// All component will be wrapped in ReactQueryEditor receiving DataSourceApi in QueryRow.renderQueryEditor
// The init() action will be removed and the store will be created in ReactQueryEditor. Note that properties
// passed to React component in QueryRow.renderQueryEditor are different than properties passed to Angular editor
// and will be mapped/provided in a way described below:
const deps = {
// WIP: to be removed. It's not passed to ReactQueryEditor but it's used only to:
// - get refId of the query (refId be passed in query property),
// - and to refresh changes (this will be handled by onChange passed to ReactQueryEditor)
// - it's needed to get other targets to interpolate the query (this will be added in QueryRow)
panelCtrl: this.panelCtrl,
// WIP: to be replaced with query property passed to ReactQueryEditor
target: this.target,
// WIP: same object will be passed to ReactQueryEditor
datasource: this.datasource,
// This is used to create view models for Angular <metric-segment> component (view models are MetricSegment objects)
// It will be simplified to produce data needed by React <SegmentAsync/> component
uiSegmentSrv: this.uiSegmentSrv,
// WIP: will be replaced with:
// import { getTemplateSrv } from 'app/features/templating/template_srv';
templateSrv: this.templateSrv,
};
const [dispatch, state] = createStore((state) => {
this.state = state;
// HACK: inefficient but not invoked frequently. It's needed to inform angular watcher about state changes
// for state shared between React/AngularJS. Actions invoked from React component will not mark the scope
// as dirty and the view won't be updated. It has to happen manually on each state change.
this.$scope.$digest();
});
this.state = state;
this.dispatch = dispatch;
this.dispatch(actions.init(deps as GraphiteQueryEditorAngularDependencies));
}
parseTarget() {
// WIP: moved to state/helpers (the same name)
}
async toggleEditorMode() {
await this.dispatch(actions.toggleEditorMode());
}
buildSegments(modifyLastSegment = true) {
// WIP: moved to state/helpers (the same name)
}
addSelectMetricSegment() {
// WIP: moved to state/helpers (the same name)
}
checkOtherSegments(fromIndex: number, modifyLastSegment = true) {
// WIP: moved to state/helpers (the same name)
}
setSegmentFocus(segmentIndex: any) {
// WIP: moved to state/helpers (the same name)
}
/**
* Get list of options for an empty segment or a segment with metric when it's clicked/opened.
*
* This is used for new segments and segments with metrics selected.
*/
async getAltSegments(index: number, text: string): Promise<GraphiteSegment[]> {
return await getAltSegments(this.state, index, text);
}
addAltTagSegments(prefix: string, altSegments: any[]) {
// WIP: moved to state/providers (the same name)
}
removeTaggedEntry(altSegments: any[]) {
// WIP: moved to state/providers (the same name)
}
/**
* Apply changes to a given metric segment
*/
async segmentValueChanged(segment: GraphiteSegment, index: number) {
await this.dispatch(actions.segmentValueChanged({ segment, index }));
}
spliceSegments(index: any) {
// WIP: moved to state/helpers (the same name)
}
emptySegments() {
// WIP: moved to state/helpers (the same name)
}
async targetTextChanged(event: ChangeEvent<HTMLInputElement>) {
await this.dispatch(actions.updateQuery({ query: event.target.value }));
}
updateModelTarget() {
// WIP: moved to state/helpers as handleTargetChanged()
}
async addFunction(name: string) {
await this.dispatch(actions.addFunction({ name }));
}
removeFunction(func: any) {
// WIP: converted to "removeFunction" action and handled in state/store reducer
// It's now dispatched in func_editor
}
moveFunction(func: any, offset: any) {
// WIP: converted to "moveFunction" action and handled in state/store reducer
// It's now dispatched in func_editor
}
addSeriesByTagFunc(tag: string) {
// WIP: moved to state/helpers (the same name)
// It's now dispatched in func_editor
}
smartlyHandleNewAliasByNode(func: { def: { name: string }; params: number[]; added: boolean }) {
// WIP: moved to state/helpers (the same name)
}
getAllTags() {
// WIP: removed. It was not used.
}
/**
* Get list of tags for editing exiting tag with <gf-form-dropdown>
*/
async getTags(index: number, query: string): Promise<AngularDropdownOptions[]> {
return await getTags(this.state, index, query);
}
/**
* Get tag list when adding a new tag with <metric-segment>
*/
async getTagsAsSegments(query: string): Promise<GraphiteSegment[]> {
return await getTagsAsSegments(this.state, query);
}
/**
* Get list of available tag operators
*/
getTagOperators(): AngularDropdownOptions[] {
return getTagOperators();
}
getAllTagValues(tag: { key: any }) {
// WIP: removed. It was not used.
}
/**
* Get list of available tag values
*/
async getTagValues(tag: GraphiteTag, index: number, query: string): Promise<AngularDropdownOptions[]> {
return await getTagValues(this.state, tag, index, query);
}
/**
* Apply changes when a tag is changed
*/
async tagChanged(tag: GraphiteTag, index: number) {
await this.dispatch(actions.tagChanged({ tag, index }));
}
async addNewTag(segment: GraphiteSegment) {
await this.dispatch(actions.addNewTag({ segment }));
}
removeTag(index: any) {
// WIP: removed. It was not used.
// Tags are removed by selecting the segment called "-- remove tag --"
}
fixTagSegments() {
// WIP: moved to state/helpers (the same name)
}
showDelimiter(index: number) {
// WIP: removed. It was not used because of broken syntax in the template. The logic has been moved directly to the template
}
pause() {
// WIP: moved to state/helpers (the same name)
}
async unpause() {
await this.dispatch(actions.unpause());
}
getCollapsedText() {
// WIP: removed. It was not used.
}
handleTagsAutoCompleteError(error: Error): void {
// WIP: moved to state/helpers (the same name)
}
handleMetricsAutoCompleteError(error: Error): void {
// WIP: moved to state/helpers (the same name)
}
}
// WIP: moved to state/providers (the same names)
// function mapToDropdownOptions(results: any[]) {}
// function removeTagPrefix(value: string): string {}