From cdb09127fbfc2753bb7c28873614ba48f6f628c9 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 18 May 2020 14:13:12 +0300 Subject: [PATCH] Problems: navigate to Explore button, #948 --- .../components/Problems/ProblemDetails.tsx | 40 ++++++++++- .../components/Problems/Problems.tsx | 2 + src/panel-triggers/triggers_panel_ctrl.ts | 1 + src/panel-triggers/utils.ts | 70 +++++++++++++++++++ src/sass/_panel-problems.scss | 26 +++++++ 5 files changed, 138 insertions(+), 1 deletion(-) diff --git a/src/panel-triggers/components/Problems/ProblemDetails.tsx b/src/panel-triggers/components/Problems/ProblemDetails.tsx index 13009cd..70fb2ff 100644 --- a/src/panel-triggers/components/Problems/ProblemDetails.tsx +++ b/src/panel-triggers/components/Problems/ProblemDetails.tsx @@ -1,6 +1,8 @@ import React, { PureComponent } from 'react'; import moment from 'moment'; import * as utils from '../../../datasource-zabbix/utils'; +import { MODE_ITEMID } from '../../../datasource-zabbix/constants'; +import { ProblemDTO, ZBXHost, ZBXGroup, ZBXEvent, ZBXTag, ZBXAlert } from '../../../datasource-zabbix/types'; import { ZBXItem, ZBXAcknowledge, GFTimeRange, RTRow } from '../../types'; import { Modal, AckProblemData } from '../Modal'; import EventTag from '../EventTag'; @@ -9,12 +11,14 @@ import ProblemStatusBar from './ProblemStatusBar'; import AcknowledgesList from './AcknowledgesList'; import ProblemTimeline from './ProblemTimeline'; import FAIcon from '../FAIcon'; -import { ProblemDTO, ZBXHost, ZBXGroup, ZBXEvent, ZBXTag, ZBXAlert } from '../../../datasource-zabbix/types'; +import { renderUrl } from '../../utils'; +import { getLocationSrv } from '@grafana/runtime'; interface ProblemDetailsProps extends RTRow { rootWidth: number; timeRange: GFTimeRange; showTimeline?: boolean; + panelId?: number; getProblemEvents: (problem: ProblemDTO) => Promise; getProblemAlerts: (problem: ProblemDTO) => Promise; onProblemAck?: (problem: ProblemDTO, data: AckProblemData) => Promise | any; @@ -89,6 +93,25 @@ export default class ProblemDetails extends PureComponent { + const problem = this.props.original as ProblemDTO; + const itemids = problem.items?.map(p => p.itemid).join(','); + + const state: any = { + datasource: problem.datasource, + context: 'explore', + originPanelId: this.props.panelId, + queries: [{ + queryType: MODE_ITEMID, + itemids: itemids, + }], + }; + + const exploreState = JSON.stringify(state); + const url = renderUrl('/explore', { left: exploreState }); + getLocationSrv().update({ path: url, query: {} }); + }; + render() { const problem = this.props.original as ProblemDTO; const alerts = this.state.alerts; @@ -110,6 +133,7 @@ export default class ProblemDetails extends PureComponent {problem.items && } + {problem.showAckButton &&
@@ -273,3 +297,17 @@ class ProblemActionButton extends PureComponent { return button; } } + +interface ExploreButtonProps { + onClick: (event?) => void; +} + +const ExploreButton: React.FC = ({ onClick }) => { + return ( + + + + ); +}; diff --git a/src/panel-triggers/components/Problems/Problems.tsx b/src/panel-triggers/components/Problems/Problems.tsx index 56e071c..4cec0ca 100644 --- a/src/panel-triggers/components/Problems/Problems.tsx +++ b/src/panel-triggers/components/Problems/Problems.tsx @@ -21,6 +21,7 @@ export interface ProblemListProps { timeRange?: GFTimeRange; pageSize?: number; fontSize?: number; + panelId?: number; getProblemEvents: (problem: ProblemDTO) => Promise; getProblemAlerts: (problem: ProblemDTO) => Promise; onProblemAck?: (problem: ProblemDTO, data: AckProblemData) => void; @@ -168,6 +169,7 @@ export default class ProblemList extends PureComponent { }); }); }; + +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(/%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('&'); +} diff --git a/src/sass/_panel-problems.scss b/src/sass/_panel-problems.scss index e0277a0..67a8c75 100644 --- a/src/sass/_panel-problems.scss +++ b/src/sass/_panel-problems.scss @@ -339,6 +339,32 @@ } } + .problem-explore-button { + &.btn { + width: 6rem; + height: 2rem; + + background-image: none; + background-color: $action-button-color; + border: 1px solid darken($action-button-color, 6%); + border-radius: 1px; + + margin-right: 1.6rem; + + span { + color: $action-button-text-color; + } + + i { + vertical-align: middle; + } + + &:hover { + background-color: darken($action-button-color, 4%); + } + } + } + .problem-details-middle { flex: 1 0 auto; overflow: auto;