From 8499d726b29a9f57c1dff30bb08be8945b34e5fa Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 9 Aug 2021 17:55:37 +0300 Subject: [PATCH] Fix Explore button, fixes #1240 Also pass dashboard time range to the explore view. --- .../ExploreButton/ExploreButton.tsx | 23 ++- .../components/Problems/ProblemDetails.tsx | 173 +++++++++--------- .../components/Problems/Problems.tsx | 69 +++---- src/panel-triggers/triggers_panel_ctrl.ts | 36 ++-- src/panel-triggers/utils.ts | 71 ------- 5 files changed, 155 insertions(+), 217 deletions(-) diff --git a/src/components/ExploreButton/ExploreButton.tsx b/src/components/ExploreButton/ExploreButton.tsx index 2396910..9a228a1 100644 --- a/src/components/ExploreButton/ExploreButton.tsx +++ b/src/components/ExploreButton/ExploreButton.tsx @@ -1,25 +1,26 @@ import React, { FC } from 'react'; -import { getLocationSrv } from '@grafana/runtime'; -import { MODE_METRICS, MODE_ITEMID } from '../../datasource-zabbix/constants'; -import { renderUrl } from '../../panel-triggers/utils'; +import { locationService } from '@grafana/runtime'; +import { ExploreUrlState, TimeRange, urlUtil } from "@grafana/data"; +import { MODE_ITEMID, MODE_METRICS } from '../../datasource-zabbix/constants'; +import { ActionButton } from '../ActionButton/ActionButton'; import { expandItemName } from '../../datasource-zabbix/utils'; import { ProblemDTO } from '../../datasource-zabbix/types'; -import { ActionButton } from '../ActionButton/ActionButton'; interface Props { problem: ProblemDTO; + range: TimeRange; panelId: number; } -export const ExploreButton: FC = ({ problem, panelId }) => { +export const ExploreButton: FC = ({ problem, panelId, range }) => { return ( - openInExplore(problem, panelId)}> + openInExplore(problem, panelId, range)}> Explore ); }; -const openInExplore = (problem: ProblemDTO, panelId: number) => { +const openInExplore = (problem: ProblemDTO, panelId: number, range: TimeRange) => { let query: any = {}; if (problem.items?.length === 1 && problem.hosts?.length === 1) { @@ -41,14 +42,16 @@ const openInExplore = (problem: ProblemDTO, panelId: number) => { }; } - const state: any = { + const state: ExploreUrlState = { datasource: problem.datasource, context: 'explore', originPanelId: panelId, + range: range.raw, queries: [query], }; const exploreState = JSON.stringify(state); - const url = renderUrl('/explore', { left: exploreState }); - getLocationSrv().update({ path: url, query: {} }); + const url = urlUtil.renderUrl('/explore', { left: exploreState }); + locationService.push(url); }; + diff --git a/src/panel-triggers/components/Problems/ProblemDetails.tsx b/src/panel-triggers/components/Problems/ProblemDetails.tsx index 37c076b..d307a6e 100644 --- a/src/panel-triggers/components/Problems/ProblemDetails.tsx +++ b/src/panel-triggers/components/Problems/ProblemDetails.tsx @@ -1,27 +1,30 @@ import React, { FC, PureComponent } from 'react'; import moment from 'moment'; import * as utils from '../../../datasource-zabbix/utils'; -import { ProblemDTO, ZBXHost, ZBXGroup, ZBXEvent, ZBXTag, ZBXAlert } from '../../../datasource-zabbix/types'; -import { ZBXScript, APIExecuteScriptResponse } from '../../../datasource-zabbix/zabbix/connectors/zabbix_api/types'; -import { ZBXItem, GFTimeRange, RTRow } from '../../types'; +import { ProblemDTO, ZBXAlert, ZBXEvent, ZBXGroup, ZBXHost, ZBXTag } from '../../../datasource-zabbix/types'; +import { APIExecuteScriptResponse, ZBXScript } from '../../../datasource-zabbix/zabbix/connectors/zabbix_api/types'; +import { GFTimeRange, RTRow, ZBXItem } from '../../types'; import { AckModal, AckProblemData } from '../AckModal'; import EventTag from '../EventTag'; import ProblemStatusBar from './ProblemStatusBar'; import AcknowledgesList from './AcknowledgesList'; import ProblemTimeline from './ProblemTimeline'; -import { FAIcon, ExploreButton, AckButton, Tooltip, ModalController, ExecScriptButton } from '../../../components'; -import { ExecScriptModal, ExecScriptData } from '../ExecScriptModal'; -import { Icon } from '@grafana/ui'; +import { AckButton, ExecScriptButton, ExploreButton, FAIcon, ModalController, Tooltip } from '../../../components'; +import { ExecScriptData, ExecScriptModal } from '../ExecScriptModal'; +import { TimeRange } from "@grafana/data"; interface ProblemDetailsProps extends RTRow { rootWidth: number; timeRange: GFTimeRange; + range: TimeRange; showTimeline?: boolean; panelId?: number; getProblemEvents: (problem: ProblemDTO) => Promise; getProblemAlerts: (problem: ProblemDTO) => Promise; getScripts: (problem: ProblemDTO) => Promise; + onExecuteScript(problem: ProblemDTO, scriptid: string): Promise; + onProblemAck?: (problem: ProblemDTO, data: AckProblemData) => Promise | any; onTagClick?: (tag: ZBXTag, datasource: string, ctrlKey?: boolean, shiftKey?: boolean) => void; } @@ -56,7 +59,7 @@ export class ProblemDetails extends PureComponent { const problem = this.props.original as ProblemDTO; return this.props.onProblemAck(problem, data); - } + }; getScripts = () => { const problem = this.props.original as ProblemDTO; return this.props.getScripts(problem); - } + }; onExecuteScript = (data: ExecScriptData) => { const problem = this.props.original as ProblemDTO; return this.props.onExecuteScript(problem, data.scriptid); - } + }; render() { const problem = this.props.original as ProblemDTO; const alerts = this.state.alerts; - const rootWidth = this.props.rootWidth; + const { rootWidth, panelId, range } = this.props; const displayClass = this.state.show ? 'show' : ''; const wideLayout = rootWidth > 1200; const compactStatusBar = rootWidth < 800 || problem.acknowledges && wideLayout && rootWidth < 1400; @@ -104,107 +107,107 @@ export class ProblemDetails extends PureComponent
- +
{problem.showAckButton && -
- - {({ showModal, hideModal }) => ( - { - showModal(ExecScriptModal, { - getScripts: this.getScripts, - onSubmit: this.onExecuteScript, - onDismiss: hideModal, - }); - }} - /> - )} - - - {({ showModal, hideModal }) => ( - { - showModal(AckModal, { - canClose: problem.manual_close === '1', - severity: problemSeverity, - onSubmit: this.ackProblem, - onDismiss: hideModal, - }); - }} - /> - )} - -
+
+ + {({ showModal, hideModal }) => ( + { + showModal(ExecScriptModal, { + getScripts: this.getScripts, + onSubmit: this.onExecuteScript, + onDismiss: hideModal, + }); + }} + /> + )} + + + {({ showModal, hideModal }) => ( + { + showModal(AckModal, { + canClose: problem.manual_close === '1', + severity: problemSeverity, + onSubmit: this.ackProblem, + onDismiss: hideModal, + }); + }} + /> + )} + +
} - +
- + {age}
- {problem.items && } + {problem.items && }
{problem.comments && -
-
- - Description:  - - {problem.comments} -
+
+
+ + Description:  + + {problem.comments}
+
} {problem.tags && problem.tags.length > 0 && -
- {problem.tags && problem.tags.map(tag => - ) - } -
+
+ {problem.tags && problem.tags.map(tag => + ) + } +
} {this.props.showTimeline && this.state.events.length > 0 && - + } {showAcknowledges && !wideLayout && -
-
Acknowledges
- -
+
+
Acknowledges
+ +
}
{showAcknowledges && wideLayout && -
-
-
Acknowledges
- -
+
+
+
Acknowledges
+
+
}
- + {problem.datasource}
{problem.proxy && -
- - {problem.proxy} -
+
+ + {problem.proxy} +
} - {problem.groups && } - {problem.hosts && } + {problem.groups && } + {problem.hosts && }
@@ -224,7 +227,7 @@ function ProblemItem(props: ProblemItemProps) { return (
- + {showName && {item.name}: } {item.lastvalue} @@ -241,8 +244,8 @@ const ProblemItems: FC = ({ items }) => { return (
{items.length > 1 ? - items.map(item => ) : - + items.map(item => ) : + }
); @@ -257,7 +260,7 @@ class ProblemGroups extends PureComponent { render() { return this.props.groups.map(g => (
- + {g.name}
)); @@ -273,7 +276,7 @@ class ProblemHosts extends PureComponent { render() { return this.props.hosts.map(h => (
- + {h.name}
)); diff --git a/src/panel-triggers/components/Problems/Problems.tsx b/src/panel-triggers/components/Problems/Problems.tsx index 360c9e7..43bff14 100644 --- a/src/panel-triggers/components/Problems/Problems.tsx +++ b/src/panel-triggers/components/Problems/Problems.tsx @@ -7,17 +7,19 @@ import { isNewProblem } from '../../utils'; import EventTag from '../EventTag'; import { ProblemDetails } from './ProblemDetails'; import { AckProblemData } from '../AckModal'; -import { GFHeartIcon, FAIcon } from '../../../components'; -import { ProblemsPanelOptions, GFTimeRange, RTCell, TriggerSeverity, RTResized } from '../../types'; -import { ProblemDTO, ZBXEvent, ZBXTag, ZBXAlert } from '../../../datasource-zabbix/types'; -import { ZBXScript, APIExecuteScriptResponse } from '../../../datasource-zabbix/zabbix/connectors/zabbix_api/types'; +import { FAIcon, GFHeartIcon } from '../../../components'; +import { GFTimeRange, ProblemsPanelOptions, RTCell, RTResized, TriggerSeverity } from '../../types'; +import { ProblemDTO, ZBXAlert, ZBXEvent, ZBXTag } from '../../../datasource-zabbix/types'; +import { APIExecuteScriptResponse, ZBXScript } from '../../../datasource-zabbix/zabbix/connectors/zabbix_api/types'; import { AckCell } from './AckCell'; +import { TimeRange } from "@grafana/data"; export interface ProblemListProps { problems: ProblemDTO[]; panelOptions: ProblemsPanelOptions; loading?: boolean; timeRange?: GFTimeRange; + range?: TimeRange; pageSize?: number; fontSize?: number; panelId?: number; @@ -52,26 +54,26 @@ export default class ProblemList extends PureComponent { this.rootRef = ref; - } + }; handleProblemAck = (problem: ProblemDTO, data: AckProblemData) => { return this.props.onProblemAck(problem, data); - } + }; onExecuteScript = (problem: ProblemDTO, data: AckProblemData) => { - } + }; handlePageSizeChange = (pageSize, pageIndex) => { if (this.props.onPageSizeChange) { this.props.onPageSizeChange(pageSize, pageIndex); } - } + }; handleResizedChange = (newResized, event) => { if (this.props.onColumnResize) { this.props.onColumnResize(newResized); } - } + }; handleExpandedChange = (expanded: any, event: any) => { const { problems, pageSize } = this.props; @@ -99,13 +101,13 @@ export default class ProblemList extends PureComponent { if (this.props.onTagClick) { this.props.onTagClick(tag, datasource, ctrlKey, shiftKey); } - } + }; getExpandedPage = (page: number) => { const { problems, pageSize } = this.props; @@ -124,7 +126,7 @@ export default class ProblemList extends PureComponent StatusCell(props, highlightNewerThan); const statusIconCell = props => StatusIconCell(props, highlightNewerThan); - const hostNameCell = props => ; - const hostTechNameCell = props => ; + const hostNameCell = props => ; + const hostTechNameCell = props => ; const columns = [ { Header: 'Host', id: 'host', show: options.hostField, Cell: hostNameCell }, @@ -152,14 +154,14 @@ export default class ProblemList extends PureComponent }, { Header: 'Tags', accessor: 'tags', show: options.showTags, className: 'problem-tags', - Cell: props => + Cell: props => }, { Header: 'Age', className: 'problem-age', width: 100, show: options.ageField, accessor: 'timestamp', @@ -207,17 +209,18 @@ export default class ProblemList extends PureComponent } expanded={this.getExpandedPage(this.state.page)} @@ -240,7 +243,7 @@ const HostCell: React.FC = ({ name, maintenance }) => { return (
{name} - {maintenance && } + {maintenance && }
); }; @@ -251,7 +254,7 @@ function SeverityCell( markAckEvents?: boolean, ackEventColor?: string, okColor = DEFAULT_OK_COLOR - ) { +) { const problem = props.original; let color: string; @@ -270,7 +273,7 @@ function SeverityCell( } return ( -
+
{severityDesc.severity}
); @@ -302,7 +305,7 @@ function StatusIconCell(props: RTCell, highlightNewerThan?: string) { 'zbx-problem': props.value === '1' }, { 'zbx-ok': props.value === '0' }, ); - return ; + return ; } function GroupCell(props: RTCell) { @@ -350,12 +353,12 @@ class TagCell extends PureComponent { if (this.props.onTagClick) { this.props.onTagClick(tag, this.props.original.datasource, ctrlKey, shiftKey); } - } + }; render() { const tags = this.props.value || []; return [ - tags.map(tag => ) + tags.map(tag => ) ]; } } diff --git a/src/panel-triggers/triggers_panel_ctrl.ts b/src/panel-triggers/triggers_panel_ctrl.ts index 2e57fcc..c6e1b94 100644 --- a/src/panel-triggers/triggers_panel_ctrl.ts +++ b/src/panel-triggers/triggers_panel_ctrl.ts @@ -4,10 +4,9 @@ import _ from 'lodash'; import { getDataSourceSrv } from '@grafana/runtime'; import { PanelEvents } from '@grafana/data'; import * as dateMath from 'grafana/app/core/utils/datemath'; -import * as utils from '../datasource-zabbix/utils'; import { MetricsPanelCtrl } from 'grafana/app/plugins/sdk'; import { triggerPanelOptionsTab } from './options_tab'; -import { migratePanelSchema, CURRENT_SCHEMA_VERSION } from './migrations'; +import { CURRENT_SCHEMA_VERSION, migratePanelSchema } from './migrations'; import ProblemList from './components/Problems/Problems'; import AlertList from './components/AlertList/AlertList'; import { ProblemDTO } from 'datasource-zabbix/types'; @@ -15,22 +14,22 @@ import { ProblemDTO } from 'datasource-zabbix/types'; const PROBLEM_EVENTS_LIMIT = 100; export const DEFAULT_TARGET = { - group: {filter: ""}, - host: {filter: ""}, - application: {filter: ""}, - trigger: {filter: ""}, - tags: {filter: ""}, - proxy: {filter: ""}, + group: { filter: "" }, + host: { filter: "" }, + application: { filter: "" }, + trigger: { filter: "" }, + tags: { filter: "" }, + proxy: { filter: "" }, showProblems: 'problems', }; export const DEFAULT_SEVERITY = [ - { priority: 0, severity: 'Not classified', color: 'rgb(108, 108, 108)', show: true}, - { priority: 1, severity: 'Information', color: 'rgb(120, 158, 183)', show: true}, - { priority: 2, severity: 'Warning', color: 'rgb(175, 180, 36)', show: true}, - { priority: 3, severity: 'Average', color: 'rgb(255, 137, 30)', show: true}, - { priority: 4, severity: 'High', color: 'rgb(255, 101, 72)', show: true}, - { priority: 5, severity: 'Disaster', color: 'rgb(215, 0, 0)', show: true}, + { priority: 0, severity: 'Not classified', color: 'rgb(108, 108, 108)', show: true }, + { priority: 1, severity: 'Information', color: 'rgb(120, 158, 183)', show: true }, + { priority: 2, severity: 'Warning', color: 'rgb(175, 180, 36)', show: true }, + { priority: 3, severity: 'Average', color: 'rgb(255, 137, 30)', show: true }, + { priority: 4, severity: 'High', color: 'rgb(255, 101, 72)', show: true }, + { priority: 5, severity: 'Disaster', color: 'rgb(215, 0, 0)', show: true }, ]; export const getDefaultSeverity = () => DEFAULT_SEVERITY; @@ -257,7 +256,7 @@ export class TriggerPanelCtrl extends MetricsPanelCtrl { let tags = _.map(tagStr.split(','), (tag) => tag.trim()); tags = _.map(tags, (tag) => { const tagParts = tag.split(':'); - return {tag: tagParts[0].trim(), value: tagParts[1].trim()}; + return { tag: tagParts[0].trim(), value: tagParts[1].trim() }; }); return tags; } @@ -271,7 +270,7 @@ export class TriggerPanelCtrl extends MetricsPanelCtrl { if (target.datasource === datasource || this.panel.datasource === datasource) { const tagFilter = target.tags.filter; let targetTags = this.parseTags(tagFilter); - const newTag = {tag: tag.tag, value: tag.value}; + const newTag = { tag: tag.tag, value: tag.value }; targetTags.push(newTag); targetTags = _.uniqWith(targetTags, _.isEqual); const newFilter = this.tagsToString(targetTags); @@ -347,12 +346,12 @@ export class TriggerPanelCtrl extends MetricsPanelCtrl { .then((datasource: any) => { const userIsEditor = this.contextSrv.isEditor || this.contextSrv.isGrafanaAdmin; if (datasource.disableReadOnlyUsersAck && !userIsEditor) { - return Promise.reject({message: 'You have no permissions to acknowledge events.'}); + return Promise.reject({ message: 'You have no permissions to acknowledge events.' }); } if (eventid) { return datasource.zabbix.acknowledgeEvent(eventid, ack_message, action, severity); } else { - return Promise.reject({message: 'Trigger has no events. Nothing to acknowledge.'}); + return Promise.reject({ message: 'Trigger has no events. Nothing to acknowledge.' }); } }) .then(this.refresh.bind(this)) @@ -413,6 +412,7 @@ export class TriggerPanelCtrl extends MetricsPanelCtrl { problems, panelOptions, timeRange: { timeFrom, timeTo }, + range: ctrl.range, loading, pageSize, fontSize: fontSizeProp, diff --git a/src/panel-triggers/utils.ts b/src/panel-triggers/utils.ts index 7ccda39..30b038c 100644 --- a/src/panel-triggers/utils.ts +++ b/src/panel-triggers/utils.ts @@ -32,74 +32,3 @@ export const getNextRefIdChar = (queries: DataQuery[]): string => { }); }); }; - -export type UrlQueryMap = Record; - -export function renderUrl(path: string, query: UrlQueryMap | undefined): string { - if (query && Object.keys(query).length > 0) { - path += '?' + toUrlParams(query); - } - return path; -} - -function encodeURIComponentAsAngularJS(val: string, pctEncodeSpaces?: boolean) { - return encodeURIComponent(val) - .replace(/%25/gi, '%2525') // Double-encode % symbol to make it properly decoded in Explore - .replace(/%40/gi, '@') - .replace(/%3A/gi, ':') - .replace(/%24/g, '$') - .replace(/%2C/gi, ',') - .replace(/%3B/gi, ';') - .replace(/%20/g, pctEncodeSpaces ? '%20' : '+'); -} - -function toUrlParams(a: any) { - const s: any[] = []; - const rbracket = /\[\]$/; - - const isArray = (obj: any) => { - return Object.prototype.toString.call(obj) === '[object Array]'; - }; - - const add = (k: string, v: any) => { - v = typeof v === 'function' ? v() : v === null ? '' : v === undefined ? '' : v; - if (typeof v !== 'boolean') { - s[s.length] = encodeURIComponentAsAngularJS(k, true) + '=' + encodeURIComponentAsAngularJS(v, true); - } else { - s[s.length] = encodeURIComponentAsAngularJS(k, true); - } - }; - - const buildParams = (prefix: string, obj: any) => { - let i, len, key; - - if (prefix) { - if (isArray(obj)) { - for (i = 0, len = obj.length; i < len; i++) { - if (rbracket.test(prefix)) { - add(prefix, obj[i]); - } else { - buildParams(prefix, obj[i]); - } - } - } else if (obj && String(obj) === '[object Object]') { - for (key in obj) { - buildParams(prefix + '[' + key + ']', obj[key]); - } - } else { - add(prefix, obj); - } - } else if (isArray(obj)) { - for (i = 0, len = obj.length; i < len; i++) { - add(obj[i].name, obj[i].value); - } - } else { - for (key in obj) { - buildParams(key, obj[key]); - } - } - return s; - }; - - return buildParams('', a).join('&'); -}