From 797425f1b6f4aabca8b796f061875e956c5ce5bc Mon Sep 17 00:00:00 2001 From: teodosii Date: Mon, 23 Jan 2023 12:00:46 +0200 Subject: [PATCH 01/30] changes on incidents table --- grafana-plugin/src/components/Tag/Tag.tsx | 5 +- .../IncidentsFilters/IncidentsFilters.tsx | 6 +- .../src/models/alertgroup/alertgroup.ts | 2 +- .../src/models/alertgroup/alertgroup.types.ts | 2 +- .../src/pages/incident/Incident.helpers.tsx | 4 +- .../src/pages/incidents/Incidents.tsx | 9 +- .../parts/IncidentDropdown.module.scss | 41 +++++++++ .../incidents/parts/IncidentDropdown.tsx | 91 +++++++++++++++++++ grafana-plugin/src/style/utils.css | 4 + 9 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 grafana-plugin/src/pages/incidents/parts/IncidentDropdown.module.scss create mode 100644 grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx diff --git a/grafana-plugin/src/components/Tag/Tag.tsx b/grafana-plugin/src/components/Tag/Tag.tsx index 6ade4339bf..c7d9f47ec8 100644 --- a/grafana-plugin/src/components/Tag/Tag.tsx +++ b/grafana-plugin/src/components/Tag/Tag.tsx @@ -8,15 +8,16 @@ interface TagProps { color: string; className?: string; children?: any; + onClick?: () => void; } const cx = cn.bind(styles); const Tag: FC = (props) => { - const { children, color, className } = props; + const { children, color, className, onClick } = props; return ( - + {children} ); diff --git a/grafana-plugin/src/containers/IncidentsFilters/IncidentsFilters.tsx b/grafana-plugin/src/containers/IncidentsFilters/IncidentsFilters.tsx index adb549198f..ab7ecd72b7 100644 --- a/grafana-plugin/src/containers/IncidentsFilters/IncidentsFilters.tsx +++ b/grafana-plugin/src/containers/IncidentsFilters/IncidentsFilters.tsx @@ -70,7 +70,7 @@ class IncidentsFilters extends Component} description="New alert groups" title={newIncidentsCount} - selected={status.includes(IncidentStatus.New)} - onClick={this.getStatusButtonClickHandler(IncidentStatus.New)} + selected={status.includes(IncidentStatus.Firing)} + onClick={this.getStatusButtonClickHandler(IncidentStatus.Firing)} />
diff --git a/grafana-plugin/src/models/alertgroup/alertgroup.ts b/grafana-plugin/src/models/alertgroup/alertgroup.ts index bf3326351d..b010cbc2e3 100644 --- a/grafana-plugin/src/models/alertgroup/alertgroup.ts +++ b/grafana-plugin/src/models/alertgroup/alertgroup.ts @@ -316,7 +316,7 @@ export class AlertGroupStore extends BaseStore { ...this.incidentFilters, resolved: false, acknowledged: false, - status: [IncidentStatus.New], + status: [IncidentStatus.Firing], }, }); this.newIncidents = result; diff --git a/grafana-plugin/src/models/alertgroup/alertgroup.types.ts b/grafana-plugin/src/models/alertgroup/alertgroup.types.ts index b165048bba..d1e7e65c9e 100644 --- a/grafana-plugin/src/models/alertgroup/alertgroup.types.ts +++ b/grafana-plugin/src/models/alertgroup/alertgroup.types.ts @@ -3,7 +3,7 @@ import { Channel } from 'models/channel'; import { User } from 'models/user/user.types'; export enum IncidentStatus { - 'New', + 'Firing', 'Acknowledged', 'Resolved', 'Silenced', diff --git a/grafana-plugin/src/pages/incident/Incident.helpers.tsx b/grafana-plugin/src/pages/incident/Incident.helpers.tsx index 9749ea0b96..117ee5db2f 100644 --- a/grafana-plugin/src/pages/incident/Incident.helpers.tsx +++ b/grafana-plugin/src/pages/incident/Incident.helpers.tsx @@ -16,7 +16,7 @@ import { UserActions } from 'utils/authorization'; export function getIncidentStatusTag(alert: Alert) { switch (alert.status) { - case IncidentStatus.New: + case IncidentStatus.Firing: return ( @@ -175,7 +175,7 @@ export function getActionButtons(incident: AlertType, cx: any, callbacks: { [key const buttons = []; if (incident.alert_receive_channel.integration !== MaintenanceIntegration) { - if (incident.status === IncidentStatus.New) { + if (incident.status === IncidentStatus.Firing) { buttons.push( ); }; - renderStatus(record: AlertType) { - return getIncidentStatusTag(record); + renderStatus(incident: AlertType) { + return getIncidentContextMenu(incident); } renderStartedAt(alert: AlertType) { @@ -495,6 +496,8 @@ class Incidents extends React.Component }; renderActionButtons = (incident: AlertType) => { + return null; + return getActionButtons(incident, cx, { onResolve: this.getOnActionButtonClick(incident.pk, AlertAction.Resolve), onUnacknowledge: this.getOnActionButtonClick(incident.pk, AlertAction.unAcknowledge), diff --git a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.module.scss b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.module.scss new file mode 100644 index 0000000000..956ecf2ac5 --- /dev/null +++ b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.module.scss @@ -0,0 +1,41 @@ +.incident__tag { + padding: 3px 12px; + display: inline-flex; + align-items: center; + cursor: pointer; +} + +.incident__icon { + margin-right: -4px; + margin-left: 2px; +} + +.incident__options { + display: flex; + flex-direction: column; +} +.incident__option-item { + padding: 8px; + display: flex; + align-items: center; + flex-direction: row; + flex-shrink: 0; + white-space: nowrap; + border-left: 2px solid transparent; + cursor: pointer; + + &:hover { + background: var(--gray-9); + } + + &--acknowledge, + &--unacknowledge { + color: var(--warning-text-color); + } + &--firing { + color: var(--error-text-color); + } + &--resolve { + color: var(--success-text-color); + } +} diff --git a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx new file mode 100644 index 0000000000..1f35c0cd34 --- /dev/null +++ b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx @@ -0,0 +1,91 @@ +import React from 'react'; + +import { Icon, WithContextMenu } from '@grafana/ui'; +import { Alert, IncidentStatus } from 'models/alertgroup/alertgroup.types'; + +import cn from 'classnames/bind'; + +import styles from 'pages/incidents/parts/IncidentDropdown.module.scss'; + +import Tag from 'components/Tag/Tag'; +import Text from 'components/Text/Text'; + +const cx = cn.bind(styles); + +export function getIncidentContextMenu(alert: Alert) { + const getIncidentTagColor = () => { + if (alert.status === IncidentStatus.Resolved) return '#299C46'; + if (alert.status === IncidentStatus.Firing) return '#E02F44'; + if (alert.status === IncidentStatus.Acknowledged) return '#C69B06'; + return '#464C54'; + }; + + const openMenuFn = ({ openMenu }) => ( + + + {IncidentStatus[alert.status]} + + + + ); + + if (alert.status === IncidentStatus.Resolved) { + return ( + ( +
+
Firing
+
+ )} + > + {openMenuFn} +
+ ); + } + + if (alert.status === IncidentStatus.Acknowledged) { + return ( + ( +
+
Unacknowledge
+
Resolve
+
+ )} + > + {openMenuFn} +
+ ); + } + + if (alert.status === IncidentStatus.Firing) { + return ( + ( + <> +
Silence
+
Acknowledge
+
Resolve
+ + )} + > + {openMenuFn} +
+ ); + } + + // Silenced Alerts + return ( + ( +
+
Unsilence
+
Acknowledge
+
Acknowledge
+
+ )} + > + {openMenuFn} +
+ ); +} diff --git a/grafana-plugin/src/style/utils.css b/grafana-plugin/src/style/utils.css index 314abf5746..b886755685 100644 --- a/grafana-plugin/src/style/utils.css +++ b/grafana-plugin/src/style/utils.css @@ -26,6 +26,10 @@ height: 100%; } +.u-display-block { + display: block; +} + .u-flex { display: flex; flex-direction: row; From b0cf4e01a53df4cb0be2f22e0de75d3ba28b50a0 Mon Sep 17 00:00:00 2001 From: teodosii Date: Mon, 23 Jan 2023 12:40:28 +0200 Subject: [PATCH 02/30] use boundingRect to position withContextMenu manually --- grafana-plugin/src/components/Tag/Tag.tsx | 10 ++++- .../incidents/parts/IncidentDropdown.tsx | 44 ++++++++++++------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/grafana-plugin/src/components/Tag/Tag.tsx b/grafana-plugin/src/components/Tag/Tag.tsx index c7d9f47ec8..6103752417 100644 --- a/grafana-plugin/src/components/Tag/Tag.tsx +++ b/grafana-plugin/src/components/Tag/Tag.tsx @@ -8,7 +8,8 @@ interface TagProps { color: string; className?: string; children?: any; - onClick?: () => void; + onClick?: (ev) => void; + forwardedRef?: React.MutableRefObject; } const cx = cn.bind(styles); @@ -17,7 +18,12 @@ const Tag: FC = (props) => { const { children, color, className, onClick } = props; return ( - + {children} ); diff --git a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx index 1f35c0cd34..96945a4e5a 100644 --- a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx +++ b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useRef } from 'react'; import { Icon, WithContextMenu } from '@grafana/ui'; import { Alert, IncidentStatus } from 'models/alertgroup/alertgroup.types'; @@ -12,23 +12,35 @@ import Text from 'components/Text/Text'; const cx = cn.bind(styles); -export function getIncidentContextMenu(alert: Alert) { - const getIncidentTagColor = () => { - if (alert.status === IncidentStatus.Resolved) return '#299C46'; - if (alert.status === IncidentStatus.Firing) return '#E02F44'; - if (alert.status === IncidentStatus.Acknowledged) return '#C69B06'; - return '#464C54'; - }; +const getIncidentTagColor = (alert: Alert) => { + if (alert.status === IncidentStatus.Resolved) return '#299C46'; + if (alert.status === IncidentStatus.Firing) return '#E02F44'; + if (alert.status === IncidentStatus.Acknowledged) return '#C69B06'; + return '#464C54'; +}; + +function ListMenu({ alert, openMenu }: { alert: Alert; openMenu: React.MouseEventHandler }) { + const forwardedRef = useRef(); - const openMenuFn = ({ openMenu }) => ( - + return ( + { + const boundingRect = forwardedRef.current.getBoundingClientRect(); + openMenu({ pageX: boundingRect.left, pageY: boundingRect.top + boundingRect.height } as any); + }} + > {IncidentStatus[alert.status]} ); +} +export function getIncidentContextMenu(alert: Alert) { if (alert.status === IncidentStatus.Resolved) { return ( )} > - {openMenuFn} + {({ openMenu }) => } ); } @@ -53,7 +65,7 @@ export function getIncidentContextMenu(alert: Alert) {
)} > - {openMenuFn} + {({ openMenu }) => } ); } @@ -69,7 +81,7 @@ export function getIncidentContextMenu(alert: Alert) { )} > - {openMenuFn} + {({ openMenu }) => } ); } @@ -80,12 +92,12 @@ export function getIncidentContextMenu(alert: Alert) { renderMenuItems={() => (
Unsilence
-
Acknowledge
-
Acknowledge
+
Acknowledge
+
Acknowledge
)} > - {openMenuFn} + {({ openMenu }) => } ); } From 16699e0a2dd20620aa36323de774a51b906c74e3 Mon Sep 17 00:00:00 2001 From: teodosii Date: Mon, 23 Jan 2023 13:41:02 +0200 Subject: [PATCH 03/30] add silence incident dropdown, stop event propagation within contextMenu --- .../incidents/parts/IncidentDropdown.tsx | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx index 96945a4e5a..f51e6586ec 100644 --- a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx +++ b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx @@ -9,6 +9,7 @@ import styles from 'pages/incidents/parts/IncidentDropdown.module.scss'; import Tag from 'components/Tag/Tag'; import Text from 'components/Text/Text'; +import SilenceDropdown from './SilenceDropdown'; const cx = cn.bind(styles); @@ -41,11 +42,16 @@ function ListMenu({ alert, openMenu }: { alert: Alert; openMenu: React.MouseEven } export function getIncidentContextMenu(alert: Alert) { + const onClickFn = (ev: React.SyntheticEvent) => { + ev.stopPropagation(); + return false; + }; + if (alert.status === IncidentStatus.Resolved) { return ( ( -
+
Firing
)} @@ -59,7 +65,7 @@ export function getIncidentContextMenu(alert: Alert) { return ( ( -
+
Unacknowledge
Resolve
@@ -74,11 +80,19 @@ export function getIncidentContextMenu(alert: Alert) { return ( ( - <> -
Silence
+
+
+ {}} + buttonSize="sm" + /> +
Acknowledge
Resolve
- +
)} > {({ openMenu }) => } @@ -90,7 +104,7 @@ export function getIncidentContextMenu(alert: Alert) { return ( ( -
+
Unsilence
Acknowledge
Acknowledge
From 634b5699e256dceddd0d4c673c9d4e2168a80b1d Mon Sep 17 00:00:00 2001 From: teodosii Date: Wed, 25 Jan 2023 20:50:31 +0200 Subject: [PATCH 04/30] changes for incident status --- .../WithContextMenu/WithContextMenu.tsx | 47 +++++++ .../src/pages/incident/Incident.helpers.tsx | 4 +- .../src/pages/incidents/Incidents.tsx | 56 ++++---- .../incidents/parts/IncidentDropdown.tsx | 129 ++++++++++++++---- ...ropdown.tsx => SilenceCascadingSelect.tsx} | 49 +++++-- grafana-plugin/src/style/utils.css | 5 + grafana-plugin/src/style/vars.css | 1 + 7 files changed, 215 insertions(+), 76 deletions(-) create mode 100644 grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx rename grafana-plugin/src/pages/incidents/parts/{SilenceDropdown.tsx => SilenceCascadingSelect.tsx} (54%) diff --git a/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx b/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx new file mode 100644 index 0000000000..5c4e082ddd --- /dev/null +++ b/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx @@ -0,0 +1,47 @@ +import { ContextMenu } from '@grafana/ui'; +import React, { useEffect, useState } from 'react'; + +export interface WithContextMenuProps { + children: (props: { openMenu: React.MouseEventHandler }) => JSX.Element; + renderMenuItems: () => React.ReactNode; + forceIsOpen?: boolean; + focusOnOpen?: boolean; +} + +export const WithContextMenu: React.FC = ({ + children, + renderMenuItems, + forceIsOpen = false, + focusOnOpen = true, +}) => { + const [isMenuOpen, setIsMenuOpen] = useState(false || forceIsOpen); + const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 }); + + useEffect(() => { + setIsMenuOpen(forceIsOpen); + }, [forceIsOpen]); + + return ( + <> + {children({ + openMenu: (e) => { + setIsMenuOpen(true); + setMenuPosition({ + x: e.pageX, + y: e.pageY, + }); + }, + })} + + {isMenuOpen && ( + setIsMenuOpen(false)} + x={menuPosition.x} + y={menuPosition.y} + renderMenuItems={renderMenuItems} + focusOnOpen={focusOnOpen} + /> + )} + + ); +}; diff --git a/grafana-plugin/src/pages/incident/Incident.helpers.tsx b/grafana-plugin/src/pages/incident/Incident.helpers.tsx index 117ee5db2f..c2adad690a 100644 --- a/grafana-plugin/src/pages/incident/Incident.helpers.tsx +++ b/grafana-plugin/src/pages/incident/Incident.helpers.tsx @@ -10,7 +10,7 @@ import { WithPermissionControl } from 'containers/WithPermissionControl/WithPerm import { MaintenanceIntegration } from 'models/alert_receive_channel'; import { Alert as AlertType, Alert, IncidentStatus } from 'models/alertgroup/alertgroup.types'; import { User } from 'models/user/user.types'; -import SilenceDropdown from 'pages/incidents/parts/SilenceDropdown'; +import SilenceCascadingSelect from 'pages/incidents/parts/SilenceCascadingSelect'; import { move } from 'state/helpers'; import { UserActions } from 'utils/authorization'; @@ -177,7 +177,7 @@ export function getActionButtons(incident: AlertType, cx: any, callbacks: { [key if (incident.alert_receive_channel.integration !== MaintenanceIntegration) { if (incident.status === IncidentStatus.Firing) { buttons.push( - )} {'restart' in store.alertGroupStore.bulkActions && ( - this.getBulkActionClickHandler('silence', ev)} /> @@ -312,7 +312,7 @@ class Incidents extends React.Component }, { - width: '20%', + width: '35%', title: 'Title', key: 'title', render: withSkeleton(this.renderTitle), @@ -341,11 +341,6 @@ class Incidents extends React.Component key: 'users', render: withSkeleton(renderRelatedUsers), }, - { - width: '15%', - key: 'action', - render: withSkeleton(this.renderActionButtons), - }, ]; return ( @@ -427,9 +422,19 @@ class Incidents extends React.Component ); }; - renderStatus(incident: AlertType) { - return getIncidentContextMenu(incident); - } + renderStatus = (alert: AlertType) => { + return ( + + ); + }; renderStartedAt(alert: AlertType) { const m = moment(alert.started_at); @@ -495,46 +500,33 @@ class Incidents extends React.Component ); }; - renderActionButtons = (incident: AlertType) => { - return null; - - return getActionButtons(incident, cx, { - onResolve: this.getOnActionButtonClick(incident.pk, AlertAction.Resolve), - onUnacknowledge: this.getOnActionButtonClick(incident.pk, AlertAction.unAcknowledge), - onUnresolve: this.getOnActionButtonClick(incident.pk, AlertAction.unResolve), - onAcknowledge: this.getOnActionButtonClick(incident.pk, AlertAction.Acknowledge), - onSilence: this.getSilenceClickHandler(incident), - onUnsilence: this.getUnsilenceClickHandler(incident), - }); - }; - - getOnActionButtonClick = (incidentId: string, action: AlertAction) => { + getOnActionButtonClick = (incidentId: string, action: AlertAction): ((e: SyntheticEvent) => Promise) => { const { store } = this.props; return (e: SyntheticEvent) => { e.stopPropagation(); - store.alertGroupStore.doIncidentAction(incidentId, action, false); + return store.alertGroupStore.doIncidentAction(incidentId, action, false); }; }; - getSilenceClickHandler = (alert: AlertType) => { + getSilenceClickHandler = (alert: AlertType): ((value: number) => Promise) => { const { store } = this.props; return (value: number) => { - store.alertGroupStore.doIncidentAction(alert.pk, AlertAction.Silence, false, { + return store.alertGroupStore.doIncidentAction(alert.pk, AlertAction.Silence, false, { delay: value, }); }; }; - getUnsilenceClickHandler = (alert: AlertType) => { + getUnsilenceClickHandler = (alert: AlertType): ((event: any) => Promise) => { const { store } = this.props; return (event: any) => { event.stopPropagation(); - store.alertGroupStore.doIncidentAction(alert.pk, AlertAction.unSilence, false); + return store.alertGroupStore.doIncidentAction(alert.pk, AlertAction.unSilence, false); }; }; diff --git a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx index f51e6586ec..b643c1e722 100644 --- a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx +++ b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx @@ -1,7 +1,7 @@ -import React, { useRef } from 'react'; +import React, { FC, SyntheticEvent, useRef, useState } from 'react'; -import { Icon, WithContextMenu } from '@grafana/ui'; -import { Alert, IncidentStatus } from 'models/alertgroup/alertgroup.types'; +import { Icon } from '@grafana/ui'; +import { Alert, AlertAction, IncidentStatus } from 'models/alertgroup/alertgroup.types'; import cn from 'classnames/bind'; @@ -9,7 +9,8 @@ import styles from 'pages/incidents/parts/IncidentDropdown.module.scss'; import Tag from 'components/Tag/Tag'; import Text from 'components/Text/Text'; -import SilenceDropdown from './SilenceDropdown'; +import SilenceCascadingSelect from './SilenceCascadingSelect'; +import { WithContextMenu } from 'components/WithContextMenu/WithContextMenu'; const cx = cn.bind(styles); @@ -41,18 +42,56 @@ function ListMenu({ alert, openMenu }: { alert: Alert; openMenu: React.MouseEven ); } -export function getIncidentContextMenu(alert: Alert) { - const onClickFn = (ev: React.SyntheticEvent) => { - ev.stopPropagation(); - return false; +export const IncidentDropdown: FC<{ + alert: Alert; + onResolve: (e: SyntheticEvent) => Promise; + onUnacknowledge: (e: SyntheticEvent) => Promise; + onUnresolve: (e: SyntheticEvent) => Promise; + onAcknowledge: (e: SyntheticEvent) => Promise; + onSilence: (value: number) => Promise; + onUnsilence: (event: any) => Promise; +}> = ({ alert, onResolve, onUnacknowledge, onUnresolve, onAcknowledge, onSilence, onUnsilence }) => { + const [isLoading, setIsLoading] = useState(false); + const [isResolvedOpen, setIsResolvedOpen] = useState(alert.status === IncidentStatus.Resolved); + const [isAcknowledgedOpen, setIsAcknowledgedOpen] = useState(alert.status === IncidentStatus.Acknowledged); + const [isFiringOpen, setIsFiringOpen] = useState(alert.status === IncidentStatus.Firing); + const [isSilencedOpen, setIsSilencedOpen] = useState(alert.status === IncidentStatus.Silenced); + + const onClickFn = ( + ev: React.SyntheticEvent, + status: string, + action: (value: SyntheticEvent | number) => Promise + ) => { + setIsLoading(true); + action(ev) + .then(() => { + if (status === AlertAction.Resolve) { + setIsResolvedOpen(false); + } else if (status === AlertAction.Acknowledge) { + setIsAcknowledgedOpen(false); + } else if (status === AlertAction.Silence) { + setIsSilencedOpen(false); + } else if (status === AlertAction.unResolve) { + setIsFiringOpen(false); + } + }) + .finally(() => { + setIsLoading(false); + }); }; if (alert.status === IncidentStatus.Resolved) { return ( ( -
-
Firing
+
+
onClickFn(e, AlertAction.Resolve, onUnresolve)} + > + Firing +
)} > @@ -64,10 +103,21 @@ export function getIncidentContextMenu(alert: Alert) { if (alert.status === IncidentStatus.Acknowledged) { return ( ( -
-
Unacknowledge
-
Resolve
+
+
onClickFn(e, AlertAction.Acknowledge, onUnacknowledge)} + > + Unacknowledge +
+
onClickFn(e, AlertAction.Acknowledge, onResolve)} + > + Resolve +
)} > @@ -79,19 +129,27 @@ export function getIncidentContextMenu(alert: Alert) { if (alert.status === IncidentStatus.Firing) { return ( ( -
-
- {}} - buttonSize="sm" - /> +
+
onClickFn(e, AlertAction.unResolve, onAcknowledge)} + > + Acknowledge +
+
onClickFn(e, AlertAction.unResolve, onResolve)} + > + Resolve +
+
onClickFn(e, AlertAction.unResolve, onSilence)} + > +
-
Acknowledge
-
Resolve
)} > @@ -103,15 +161,28 @@ export function getIncidentContextMenu(alert: Alert) { // Silenced Alerts return ( ( -
-
Unsilence
-
Acknowledge
-
Acknowledge
+
+
onClickFn(e, AlertAction.Silence, onUnsilence)}> + Unsilence +
+
onClickFn(e, AlertAction.Silence, onAcknowledge)} + > + Acknowledge +
+
onClickFn(e, AlertAction.Silence, onAcknowledge)} + > + Acknowledge +
)} > {({ openMenu }) => } ); -} +}; diff --git a/grafana-plugin/src/pages/incidents/parts/SilenceDropdown.tsx b/grafana-plugin/src/pages/incidents/parts/SilenceCascadingSelect.tsx similarity index 54% rename from grafana-plugin/src/pages/incidents/parts/SilenceDropdown.tsx rename to grafana-plugin/src/pages/incidents/parts/SilenceCascadingSelect.tsx index 4aa903d554..b6b7e33740 100644 --- a/grafana-plugin/src/pages/incidents/parts/SilenceDropdown.tsx +++ b/grafana-plugin/src/pages/incidents/parts/SilenceCascadingSelect.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react'; -import { ButtonCascader, ComponentSize } from '@grafana/ui'; +import { ButtonCascader, ComponentSize, Select } from '@grafana/ui'; import { observer } from 'mobx-react'; import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl'; @@ -8,15 +8,17 @@ import { SelectOption } from 'state/types'; import { useStore } from 'state/useStore'; import { UserActions } from 'utils/authorization'; -interface SilenceDropdownProps { - onSelect: (value: number) => void; +interface SilenceCascadingSelectProps { + isCascading?: boolean; className?: string; disabled?: boolean; buttonSize?: string; + + onSelect: (value: number) => void; } -const SilenceDropdown = observer((props: SilenceDropdownProps) => { - const { onSelect, className, disabled = false, buttonSize } = props; +const SilenceCascadingSelect = observer((props: SilenceCascadingSelectProps) => { + const { onSelect, isCascading = true, className, disabled = false, buttonSize } = props; const onSelectCallback = useCallback( ([value]) => { @@ -33,23 +35,44 @@ const SilenceDropdown = observer((props: SilenceDropdownProps) => { return ( + {isCascading ? renderAsCascader() : renderAsSelectDropdown()} + + ); + + function renderAsCascader() { + return ( ({ - value: silenceOption.value, - label: silenceOption.display_name, - }))} + options={getOptions()} value={undefined} buttonProps={{ size: buttonSize as ComponentSize }} > Silence - - ); + ); + } + + function renderAsSelectDropdown() { + return ( + onSelect(Number(value))} options={getOptions()} /> ); From e0c75e862d901544502b33ecef981cd10da38ef2 Mon Sep 17 00:00:00 2001 From: teodosii Date: Fri, 27 Jan 2023 09:56:55 +0200 Subject: [PATCH 08/30] added match media tooltip, fixed layout responsiveness in alert rules, fixed table responsiveness in alert groups --- .../MatchMediaTooltip/MatchMediaTooltip.tsx | 37 ++++ .../components/Tutorial/Tutorial.module.css | 12 +- .../AlertRules/AlertRules.module.css | 30 ++- .../src/containers/AlertRules/AlertRules.tsx | 183 +++++++++--------- .../src/pages/incident/Incident.helpers.tsx | 66 ++++--- ...dents.module.css => Incidents.module.scss} | 0 .../src/pages/incidents/Incidents.tsx | 76 ++------ .../src/plugin/GrafanaPluginRootPage.tsx | 1 + grafana-plugin/src/style/tables.css | 15 ++ grafana-plugin/src/utils/consts.ts | 2 + 10 files changed, 226 insertions(+), 196 deletions(-) create mode 100644 grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx rename grafana-plugin/src/pages/incidents/{Incidents.module.css => Incidents.module.scss} (100%) create mode 100644 grafana-plugin/src/style/tables.css diff --git a/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx new file mode 100644 index 0000000000..472fde7c53 --- /dev/null +++ b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx @@ -0,0 +1,37 @@ +import { Tooltip } from '@grafana/ui'; +import React, { FC } from 'react'; + +interface MediaMatchTooltipProps { + placement: 'top' | 'bottom' | 'right' | 'left'; + content: string; + children: JSX.Element; + + maxWidth?: number; + minWidth?: number; +} + +const MediaMatchTooltip: FC = ({ minWidth, maxWidth, placement, content, children }) => { + let match: MediaQueryList; + + if (minWidth && maxWidth) { + match = window.matchMedia(`(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`); + } else if (minWidth) { + match = window.matchMedia(`(min-width: ${minWidth}px)`); + } else if (maxWidth) { + match = window.matchMedia(`(max-width: ${maxWidth}px)`); + } else { + return <>{children}; + } + + if (match) { + return ( + + {children} + + ); + } + + return <>{children}; +}; + +export default MediaMatchTooltip; diff --git a/grafana-plugin/src/components/Tutorial/Tutorial.module.css b/grafana-plugin/src/components/Tutorial/Tutorial.module.css index bb3b2c797c..67d607aab8 100644 --- a/grafana-plugin/src/components/Tutorial/Tutorial.module.css +++ b/grafana-plugin/src/components/Tutorial/Tutorial.module.css @@ -25,12 +25,6 @@ text-align: center; } -@media (min-width: 1540px) { - .step { - width: 170px; - } -} - .icon { width: 60px; height: 60px; @@ -55,3 +49,9 @@ :global(.theme-dark) .arrow svg { fill-opacity: 0.15; } + +@media (min-width: 1540px) { + .step { + width: 170px; + } +} diff --git a/grafana-plugin/src/containers/AlertRules/AlertRules.module.css b/grafana-plugin/src/containers/AlertRules/AlertRules.module.css index e85a4acb3e..37c913502e 100644 --- a/grafana-plugin/src/containers/AlertRules/AlertRules.module.css +++ b/grafana-plugin/src/containers/AlertRules/AlertRules.module.css @@ -16,12 +16,6 @@ margin-bottom: 10px; } -.header { - display: flex; - justify-content: space-between; - align-items: center; -} - .verbal-name { font-weight: 500; } @@ -116,3 +110,27 @@ .description-style a { color: var(--primary-text-link); } + +.integration__heading-text { + display: flex; + gap: 8px; +} + +.integration__heading-container { + display: flex; + flex-wrap: wrap; +} + +.integration__heading-container-left { + margin-bottom: 12px; +} + +.integration__heading-container-left, +.integration__heading-container-right { + flex-grow: 1; +} + +.integration__heading-container-right { + display: flex; + justify-content: flex-end; +} diff --git a/grafana-plugin/src/containers/AlertRules/AlertRules.tsx b/grafana-plugin/src/containers/AlertRules/AlertRules.tsx index c696a0e90b..18dc843250 100644 --- a/grafana-plugin/src/containers/AlertRules/AlertRules.tsx +++ b/grafana-plugin/src/containers/AlertRules/AlertRules.tsx @@ -155,20 +155,100 @@ class AlertRules extends React.Component { <>
-
- - - Escalate -
{parseEmojis(alertReceiveChannel?.verbal_name || '')}
- +
+
+ +
+
{parseEmojis(alertReceiveChannel?.verbal_name || '')}
+ + aaadsa dasd + +
+
+
+ +
+
+ + + + +
+ {maintenanceMode === MaintenanceMode.Debug || maintenanceMode === MaintenanceMode.Maintenance ? ( + +
+
+
{editIntegrationName !== undefined && ( {
)} -
- - - - -
- {maintenanceMode === MaintenanceMode.Debug || maintenanceMode === MaintenanceMode.Maintenance ? ( - -
-
{alertReceiveChannel.description && ( diff --git a/grafana-plugin/src/pages/incident/Incident.helpers.tsx b/grafana-plugin/src/pages/incident/Incident.helpers.tsx index c2adad690a..b20c7b951e 100644 --- a/grafana-plugin/src/pages/incident/Incident.helpers.tsx +++ b/grafana-plugin/src/pages/incident/Incident.helpers.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Button, HorizontalGroup, Icon, Tooltip, VerticalGroup } from '@grafana/ui'; +import { Button, HorizontalGroup, IconButton, Tooltip, VerticalGroup } from '@grafana/ui'; import Avatar from 'components/Avatar/Avatar'; import PluginLink from 'components/PluginLink/PluginLink'; @@ -13,6 +13,8 @@ import { User } from 'models/user/user.types'; import SilenceCascadingSelect from 'pages/incidents/parts/SilenceCascadingSelect'; import { move } from 'state/helpers'; import { UserActions } from 'utils/authorization'; +import MatchMediaTooltip from 'components/MatchMediaTooltip/MatchMediaTooltip'; +import { INCIDENTS_MATCH_MEDIA_MAX_WIDTH } from 'utils/consts'; export function getIncidentStatusTag(alert: Alert) { switch (alert.status) { @@ -65,15 +67,19 @@ export function renderRelatedUsers(incident: Alert, isFull = false) { function renderUser(user: User) { let badge = undefined; if (incident.resolved_by_user && user.pk === incident.resolved_by_user.pk) { - badge = ; + badge = ; } else if (incident.acknowledged_by_user && user.pk === incident.acknowledged_by_user.pk) { - badge = ; + badge = ; } return ( - + - {user.username} {badge} + {' '} + + {user.username} + {' '} + {badge} ); @@ -106,30 +112,32 @@ export function renderRelatedUsers(incident: Alert, isFull = false) { } return ( - - {visibleUsers.map(renderUser)} - {Boolean(otherUsers.length) && ( - - {otherUsers.map((user, index) => ( - <> - {index ? ', ' : ''} - {renderUser(user)} - - ))} - - } - > - - - +{otherUsers.length} user{otherUsers.length > 1 ? 's' : ''} - - - - )} - +
+ + {visibleUsers.map(renderUser)} + {Boolean(otherUsers.length) && ( + + {otherUsers.map((user, index) => ( + <> + {index ? ', ' : ''} + {renderUser(user)} + + ))} + + } + > + + + +{otherUsers.length} user{otherUsers.length > 1 ? 's' : ''} + + + + )} + +
); } diff --git a/grafana-plugin/src/pages/incidents/Incidents.module.css b/grafana-plugin/src/pages/incidents/Incidents.module.scss similarity index 100% rename from grafana-plugin/src/pages/incidents/Incidents.module.css rename to grafana-plugin/src/pages/incidents/Incidents.module.scss diff --git a/grafana-plugin/src/pages/incidents/Incidents.tsx b/grafana-plugin/src/pages/incidents/Incidents.tsx index 14f8360ab6..8662636305 100644 --- a/grafana-plugin/src/pages/incidents/Incidents.tsx +++ b/grafana-plugin/src/pages/incidents/Incidents.tsx @@ -1,6 +1,6 @@ import React, { ReactElement, SyntheticEvent } from 'react'; -import { Button, Icon, Tooltip, VerticalGroup, LoadingPlaceholder, HorizontalGroup } from '@grafana/ui'; +import { Button, VerticalGroup, LoadingPlaceholder, HorizontalGroup, Tooltip } from '@grafana/ui'; import cn from 'classnames/bind'; import { get } from 'lodash-es'; import { observer } from 'mobx-react'; @@ -18,9 +18,7 @@ import { IncidentsFiltersType } from 'containers/IncidentsFilters/IncidentFilter import IncidentsFilters from 'containers/IncidentsFilters/IncidentsFilters'; import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl'; import { Alert, Alert as AlertType, AlertAction } from 'models/alertgroup/alertgroup.types'; -import { User } from 'models/user/user.types'; import { renderRelatedUsers } from 'pages/incident/Incident.helpers'; -import { move } from 'state/helpers'; import { PageProps, WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; import LocationHelper from 'utils/LocationHelper'; @@ -29,7 +27,7 @@ import { UserActions } from 'utils/authorization'; import { IncidentDropdown } from './parts/IncidentDropdown'; import SilenceCascadingSelect from './parts/SilenceCascadingSelect'; -import styles from './Incidents.module.css'; +import styles from './Incidents.module.scss'; const cx = cn.bind(styles); @@ -310,7 +308,6 @@ class Incidents extends React.Component key: 'id', render: withSkeleton(this.renderId), }, - { width: '35%', title: 'Title', @@ -394,12 +391,16 @@ class Incidents extends React.Component return ( - - {record.render_for_web.title} - - {Boolean(record.dependent_alert_groups.length) && `+ ${record.dependent_alert_groups.length} attached`} +
+ + + {record.render_for_web.title} + + + {Boolean(record.dependent_alert_groups.length) && `+ ${record.dependent_alert_groups.length} attached`} +
); }; @@ -447,59 +448,6 @@ class Incidents extends React.Component ); } - renderRelatedUsers = (record: AlertType) => { - const { related_users } = record; - let users = [...related_users]; - - function renderUser(user: User, index: number) { - let badge = undefined; - if (record.resolved_by_user && user.pk === record.resolved_by_user.pk) { - badge = ; - } else if (record.acknowledged_by_user && user.pk === record.acknowledged_by_user.pk) { - badge = ; - } - - return ( - - - {index ? ', ' : ''} - {user.username} {badge} - - - ); - } - - if (record.resolved_by_user) { - const index = users.findIndex((user) => user.pk === record.resolved_by_user.pk); - if (index > -1) { - users = move(users, index, 0); - } - } - - if (record.acknowledged_by_user) { - const index = users.findIndex((user) => user.pk === record.acknowledged_by_user.pk); - if (index > -1) { - users = move(users, index, 0); - } - } - - const visibleUsers = users.slice(0, 2); - const otherUsers = users.slice(2); - - return ( - <> - {visibleUsers.map(renderUser)} - {Boolean(otherUsers.length) && ( - {otherUsers.map(renderUser)}}> - - , +{otherUsers.length} users{' '} - - - )} - - ); - }; - getOnActionButtonClick = (incidentId: string, action: AlertAction): ((e: SyntheticEvent) => Promise) => { const { store } = this.props; diff --git a/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx b/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx index 1769f0cb9c..998a306260 100644 --- a/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx +++ b/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx @@ -54,6 +54,7 @@ dayjs.extend(customParseFormat); import 'style/vars.css'; import 'style/global.css'; import 'style/utils.css'; +import 'style/tables.css'; import { getQueryParams, isTopNavbar } from './GrafanaPluginRootPage.helpers'; import PluginSetup from './PluginSetup'; diff --git a/grafana-plugin/src/style/tables.css b/grafana-plugin/src/style/tables.css new file mode 100644 index 0000000000..e69aa8d26f --- /dev/null +++ b/grafana-plugin/src/style/tables.css @@ -0,0 +1,15 @@ +@media screen and (max-width: 1600px) { + .incident__email-column { + max-width: 175px; + } + + .incident__email-content { + text-overflow: ellipsis; + overflow: hidden; + } + + .incident__title-column { + overflow-wrap: anywhere; + white-space: pre-wrap; + } +} diff --git a/grafana-plugin/src/utils/consts.ts b/grafana-plugin/src/utils/consts.ts index ddddf6cfa9..ebdc682537 100644 --- a/grafana-plugin/src/utils/consts.ts +++ b/grafana-plugin/src/utils/consts.ts @@ -30,3 +30,5 @@ export const FARO_ENDPOINT_PROD = export const DOCS_SLACK_SETUP = 'https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup'; export const DOCS_TELEGRAM_SETUP = 'https://grafana.com/docs/grafana-cloud/oncall/chat-options/configure-telegram/'; + +export const INCIDENTS_MATCH_MEDIA_MAX_WIDTH = 1400; From e5205f4c6cd1da3d94167548b14e2eb9e587a93e Mon Sep 17 00:00:00 2001 From: teodosii Date: Fri, 27 Jan 2023 10:09:24 +0200 Subject: [PATCH 09/30] use match.matches --- .../src/components/MatchMediaTooltip/MatchMediaTooltip.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx index 472fde7c53..214d0864ad 100644 --- a/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx +++ b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx @@ -23,7 +23,7 @@ const MediaMatchTooltip: FC = ({ minWidth, maxWidth, pla return <>{children}; } - if (match) { + if (match.matches) { return ( {children} From 5ca2b3a62c109941286a34657b654e7e80141968 Mon Sep 17 00:00:00 2001 From: teodosii Date: Fri, 27 Jan 2023 12:15:42 +0200 Subject: [PATCH 10/30] remove old schedules code, more responsive changes added --- .../MatchMediaTooltip/MatchMediaTooltip.tsx | 4 +- .../SchedulesFilters.module.css | 4 - .../SchedulesFilters.module.scss | 11 +++ .../SchedulesFilters/SchedulesFilters.tsx | 96 +++++++++++++------ .../SchedulesFilters.types.ts | 6 +- .../SchedulesFilters.helpers.ts | 25 ----- .../SchedulesFilters.module.css | 4 - .../SchedulesFilters_NEW/SchedulesFilters.tsx | 95 ------------------ .../SchedulesFilters.types.ts | 7 -- .../src/pages/incident/Incident.helpers.tsx | 10 +- .../src/pages/incidents/Incidents.tsx | 2 +- .../src/pages/schedules/Schedules.module.css | 16 ++++ .../src/pages/schedules/Schedules.tsx | 43 +++++---- .../src/plugin/GrafanaPluginRootPage.tsx | 2 +- .../src/style/{tables.css => responsive.css} | 4 +- grafana-plugin/src/utils/consts.ts | 2 +- 16 files changed, 133 insertions(+), 198 deletions(-) delete mode 100644 grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.css create mode 100644 grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.scss delete mode 100644 grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.helpers.ts delete mode 100644 grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.module.css delete mode 100644 grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx delete mode 100644 grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.types.ts rename grafana-plugin/src/style/{tables.css => responsive.css} (78%) diff --git a/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx index 214d0864ad..c9c2d7f48e 100644 --- a/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx +++ b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx @@ -10,7 +10,7 @@ interface MediaMatchTooltipProps { minWidth?: number; } -const MediaMatchTooltip: FC = ({ minWidth, maxWidth, placement, content, children }) => { +export const MatchMediaTooltip: FC = ({ minWidth, maxWidth, placement, content, children }) => { let match: MediaQueryList; if (minWidth && maxWidth) { @@ -33,5 +33,3 @@ const MediaMatchTooltip: FC = ({ minWidth, maxWidth, pla return <>{children}; }; - -export default MediaMatchTooltip; diff --git a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.css b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.css deleted file mode 100644 index e7bbaff642..0000000000 --- a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.root { - display: inline-flex; - align-items: center; -} diff --git a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.scss b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.scss new file mode 100644 index 0000000000..6327693a57 --- /dev/null +++ b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.scss @@ -0,0 +1,11 @@ +.root { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 4px; +} + +.right { + display: flex; + gap: 4px; +} diff --git a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx index ce99674ffa..8aee0b44cc 100644 --- a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx +++ b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx @@ -1,59 +1,95 @@ -import React, { useCallback, useMemo } from 'react'; +import React, { ChangeEvent, useCallback } from 'react'; -import { DatePickerWithInput, Field, HorizontalGroup, RadioButtonGroup } from '@grafana/ui'; +import { Field, Icon, Input, RadioButtonGroup } from '@grafana/ui'; import cn from 'classnames/bind'; -import moment from 'moment-timezone'; -import { dateStringToOption, optionToDateString } from './SchedulesFilters.helpers'; +import { ScheduleType } from 'models/schedule/schedule.types'; + import { SchedulesFiltersType } from './SchedulesFilters.types'; -import styles from './SchedulesFilters.module.css'; +import styles from './SchedulesFilters.module.scss'; const cx = cn.bind(styles); interface SchedulesFiltersProps { value: SchedulesFiltersType; onChange: (filters: SchedulesFiltersType) => void; - className?: string; } -const SchedulesFilters = ({ value, onChange, className }: SchedulesFiltersProps) => { - const handleDateChange = useCallback( - (date: Date) => { - onChange({ selectedDate: moment(date).format('YYYY-MM-DD') }); +const SchedulesFilters = (props: SchedulesFiltersProps) => { + const { value, onChange } = props; + + const onSearchTermChangeCallback = useCallback( + (e: ChangeEvent) => { + onChange({ ...value, searchTerm: e.currentTarget.value }); }, - [onChange] + [value] ); - - const option = useMemo(() => dateStringToOption(value.selectedDate), [value]); - - const handleOptionChange = useCallback( - (option: string) => { - onChange({ ...value, selectedDate: optionToDateString(option) }); + const handleStatusChange = useCallback( + (status) => { + onChange({ ...value, status }); }, - [onChange, value] + [value] ); - const datePickerValue = useMemo(() => moment(value.selectedDate).toDate(), [value]); + const handleTypeChange = useCallback( + (type) => { + onChange({ ...value, type }); + }, + [value] + ); return ( -
- - +
+
+ + } + placeholder="Search..." + value={value.searchTerm} + onChange={onSearchTermChangeCallback} + /> + +
+
+ - - + + - +
); }; diff --git a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.types.ts b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.types.ts index 4ec857f9e4..ec0ab6321b 100644 --- a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.types.ts +++ b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.types.ts @@ -1,3 +1,7 @@ +import { ScheduleType } from 'models/schedule/schedule.types'; + export interface SchedulesFiltersType { - selectedDate: string; + searchTerm: string; + type: ScheduleType; + status: string; } diff --git a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.helpers.ts b/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.helpers.ts deleted file mode 100644 index 1b35ebb3f1..0000000000 --- a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.helpers.ts +++ /dev/null @@ -1,25 +0,0 @@ -import moment from 'moment-timezone'; - -export function optionToDateString(option: string) { - switch (option) { - case 'today': - return moment().startOf('day').format('YYYY-MM-DD'); - case 'tomorrow': - return moment().add(1, 'day').startOf('day').format('YYYY-MM-DD'); - default: - return moment().add(2, 'day').startOf('day').format('YYYY-MM-DD'); - } -} - -export function dateStringToOption(dateString: string) { - const today = moment().startOf('day').format('YYYY-MM-DD'); - if (dateString === today) { - return 'today'; - } - const tomorrow = moment().add(1, 'day').startOf('day').format('YYYY-MM-DD'); - if (dateString === tomorrow) { - return 'tomorrow'; - } - - return 'custom'; -} diff --git a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.module.css b/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.module.css deleted file mode 100644 index e7bbaff642..0000000000 --- a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.root { - display: inline-flex; - align-items: center; -} diff --git a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx b/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx deleted file mode 100644 index cc6ee209da..0000000000 --- a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { ChangeEvent, useCallback } from 'react'; - -import { Field, HorizontalGroup, Icon, Input, RadioButtonGroup } from '@grafana/ui'; -import cn from 'classnames/bind'; - -import { ScheduleType } from 'models/schedule/schedule.types'; - -import { SchedulesFiltersType } from './SchedulesFilters.types'; - -import styles from './SchedulesFilters.module.css'; - -const cx = cn.bind(styles); - -interface SchedulesFiltersProps { - value: SchedulesFiltersType; - onChange: (filters: SchedulesFiltersType) => void; -} - -const SchedulesFilters = (props: SchedulesFiltersProps) => { - const { value, onChange } = props; - - const onSearchTermChangeCallback = useCallback( - (e: ChangeEvent) => { - onChange({ ...value, searchTerm: e.currentTarget.value }); - }, - [value] - ); - const handleStatusChange = useCallback( - (status) => { - onChange({ ...value, status }); - }, - [value] - ); - - const handleTypeChange = useCallback( - (type) => { - onChange({ ...value, type }); - }, - [value] - ); - - return ( -
- - - } - placeholder="Search..." - value={value.searchTerm} - onChange={onSearchTermChangeCallback} - /> - - - - - - - - -
- ); -}; - -export default SchedulesFilters; diff --git a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.types.ts b/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.types.ts deleted file mode 100644 index ec0ab6321b..0000000000 --- a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ScheduleType } from 'models/schedule/schedule.types'; - -export interface SchedulesFiltersType { - searchTerm: string; - type: ScheduleType; - status: string; -} diff --git a/grafana-plugin/src/pages/incident/Incident.helpers.tsx b/grafana-plugin/src/pages/incident/Incident.helpers.tsx index b20c7b951e..aa935b2415 100644 --- a/grafana-plugin/src/pages/incident/Incident.helpers.tsx +++ b/grafana-plugin/src/pages/incident/Incident.helpers.tsx @@ -13,8 +13,8 @@ import { User } from 'models/user/user.types'; import SilenceCascadingSelect from 'pages/incidents/parts/SilenceCascadingSelect'; import { move } from 'state/helpers'; import { UserActions } from 'utils/authorization'; -import MatchMediaTooltip from 'components/MatchMediaTooltip/MatchMediaTooltip'; -import { INCIDENTS_MATCH_MEDIA_MAX_WIDTH } from 'utils/consts'; +import { MatchMediaTooltip } from 'components/MatchMediaTooltip/MatchMediaTooltip'; +import { TABLE_COLUMN_MAX_WIDTH } from 'utils/consts'; export function getIncidentStatusTag(alert: Alert) { switch (alert.status) { @@ -73,10 +73,10 @@ export function renderRelatedUsers(incident: Alert, isFull = false) { } return ( - + {' '} - + {user.username} {' '} {badge} @@ -112,7 +112,7 @@ export function renderRelatedUsers(incident: Alert, isFull = false) { } return ( -
+
{visibleUsers.map(renderUser)} {Boolean(otherUsers.length) && ( diff --git a/grafana-plugin/src/pages/incidents/Incidents.tsx b/grafana-plugin/src/pages/incidents/Incidents.tsx index 8662636305..92db67879a 100644 --- a/grafana-plugin/src/pages/incidents/Incidents.tsx +++ b/grafana-plugin/src/pages/incidents/Incidents.tsx @@ -391,7 +391,7 @@ class Incidents extends React.Component return ( -
+
diff --git a/grafana-plugin/src/pages/schedules/Schedules.module.css b/grafana-plugin/src/pages/schedules/Schedules.module.css index bb04456369..a3da5c73da 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.module.css +++ b/grafana-plugin/src/pages/schedules/Schedules.module.css @@ -11,3 +11,19 @@ .root .buttons { padding-right: 10px; } + +.schedules__filters-container { + display: flex; + width: 100%; +} + +.schedules__filters-container-left { + flex-grow: 1; +} + +.schedules__filters-container-right { + display: flex; + justify-content: flex-end; + gap: 4px; + padding-top: 19px; +} diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index 4d1f2e2885..eb4009e115 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -12,8 +12,8 @@ import NewScheduleSelector from 'components/NewScheduleSelector/NewScheduleSelec import PluginLink from 'components/PluginLink/PluginLink'; import ScheduleCounter from 'components/ScheduleCounter/ScheduleCounter'; import ScheduleWarning from 'components/ScheduleWarning/ScheduleWarning'; -import SchedulesFilters from 'components/SchedulesFilters_NEW/SchedulesFilters'; -import { SchedulesFiltersType } from 'components/SchedulesFilters_NEW/SchedulesFilters.types'; +import SchedulesFilters from 'components/SchedulesFilters/SchedulesFilters'; +import { SchedulesFiltersType } from 'components/SchedulesFilters/SchedulesFilters.types'; import Table from 'components/Table/Table'; import Text from 'components/Text/Text'; import TimelineMarks from 'components/TimelineMarks/TimelineMarks'; @@ -29,9 +29,10 @@ import { getStartOfWeek } from 'pages/schedule/Schedule.helpers'; import { WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; import { UserActions } from 'utils/authorization'; -import { PLUGIN_ROOT } from 'utils/consts'; +import { PLUGIN_ROOT, TABLE_COLUMN_MAX_WIDTH } from 'utils/consts'; import styles from './Schedules.module.css'; +import { MatchMediaTooltip } from 'components/MatchMediaTooltip/MatchMediaTooltip'; const cx = cn.bind(styles); @@ -137,9 +138,11 @@ class SchedulesPage extends React.Component
- - - +
+
+ +
+
{users && ( - - +
+
{ if (item.on_call_now?.length > 0) { return ( - - {item.on_call_now.map((user, _index) => { - return ( - -
+
+ + {item.on_call_now.map((user, _index) => { + return ( + - {user.username} -
- - ); - })} - + + {user.username} + + + ); + })} + +
); } return null; diff --git a/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx b/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx index 998a306260..f9e72ad02d 100644 --- a/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx +++ b/grafana-plugin/src/plugin/GrafanaPluginRootPage.tsx @@ -54,7 +54,7 @@ dayjs.extend(customParseFormat); import 'style/vars.css'; import 'style/global.css'; import 'style/utils.css'; -import 'style/tables.css'; +import 'style/responsive.css'; import { getQueryParams, isTopNavbar } from './GrafanaPluginRootPage.helpers'; import PluginSetup from './PluginSetup'; diff --git a/grafana-plugin/src/style/tables.css b/grafana-plugin/src/style/responsive.css similarity index 78% rename from grafana-plugin/src/style/tables.css rename to grafana-plugin/src/style/responsive.css index e69aa8d26f..71462e32e5 100644 --- a/grafana-plugin/src/style/tables.css +++ b/grafana-plugin/src/style/responsive.css @@ -1,9 +1,9 @@ @media screen and (max-width: 1600px) { - .incident__email-column { + .table__email-column { max-width: 175px; } - .incident__email-content { + .table__email-content { text-overflow: ellipsis; overflow: hidden; } diff --git a/grafana-plugin/src/utils/consts.ts b/grafana-plugin/src/utils/consts.ts index ebdc682537..c913ff73c8 100644 --- a/grafana-plugin/src/utils/consts.ts +++ b/grafana-plugin/src/utils/consts.ts @@ -31,4 +31,4 @@ export const FARO_ENDPOINT_PROD = export const DOCS_SLACK_SETUP = 'https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup'; export const DOCS_TELEGRAM_SETUP = 'https://grafana.com/docs/grafana-cloud/oncall/chat-options/configure-telegram/'; -export const INCIDENTS_MATCH_MEDIA_MAX_WIDTH = 1400; +export const TABLE_COLUMN_MAX_WIDTH = 1400; From 17d6c762ea5ae7870b4b2bf0c77cca3bc49c0cd0 Mon Sep 17 00:00:00 2001 From: teodosii Date: Fri, 27 Jan 2023 13:17:24 +0200 Subject: [PATCH 11/30] same for schedules --- .../src/pages/schedules/Schedules.module.css | 6 ++++++ grafana-plugin/src/pages/schedules/Schedules.tsx | 12 ++++++++---- grafana-plugin/src/style/responsive.css | 6 +++++- grafana-plugin/src/utils/consts.ts | 3 ++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/grafana-plugin/src/pages/schedules/Schedules.module.css b/grafana-plugin/src/pages/schedules/Schedules.module.css index a3da5c73da..9d131884b6 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.module.css +++ b/grafana-plugin/src/pages/schedules/Schedules.module.css @@ -27,3 +27,9 @@ gap: 4px; padding-top: 19px; } + +.schedules__user-on-call { + display: flex; + flex-wrap: nowrap; + gap: 4px; +} diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index eb4009e115..15dc6c1bee 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -338,10 +338,14 @@ class SchedulesPage extends React.Component { return ( - - - {user.username} - +
+
+ +
+ + {user.username} + +
); })} diff --git a/grafana-plugin/src/style/responsive.css b/grafana-plugin/src/style/responsive.css index 71462e32e5..a619f20c1f 100644 --- a/grafana-plugin/src/style/responsive.css +++ b/grafana-plugin/src/style/responsive.css @@ -1,4 +1,8 @@ -@media screen and (max-width: 1600px) { +/* + Make sure if you chage max-width here + You also change it in consts.ts +*/ +@media screen and (max-width: 1500px) { .table__email-column { max-width: 175px; } diff --git a/grafana-plugin/src/utils/consts.ts b/grafana-plugin/src/utils/consts.ts index c913ff73c8..57e004ba6f 100644 --- a/grafana-plugin/src/utils/consts.ts +++ b/grafana-plugin/src/utils/consts.ts @@ -31,4 +31,5 @@ export const FARO_ENDPOINT_PROD = export const DOCS_SLACK_SETUP = 'https://grafana.com/docs/grafana-cloud/oncall/open-source/#slack-setup'; export const DOCS_TELEGRAM_SETUP = 'https://grafana.com/docs/grafana-cloud/oncall/chat-options/configure-telegram/'; -export const TABLE_COLUMN_MAX_WIDTH = 1400; +// Make sure if you chage max-width here you also change it in responsive.css +export const TABLE_COLUMN_MAX_WIDTH = 1500; From ef2b20e1e8fe851e1c682ec871da49d41a652753 Mon Sep 17 00:00:00 2001 From: teodosii Date: Fri, 27 Jan 2023 13:19:56 +0200 Subject: [PATCH 12/30] linter --- .../src/components/MatchMediaTooltip/MatchMediaTooltip.tsx | 3 ++- .../src/components/SchedulesFilters/SchedulesFilters.tsx | 3 +-- grafana-plugin/src/pages/incident/Incident.helpers.tsx | 2 +- grafana-plugin/src/pages/incidents/Incidents.tsx | 3 +-- grafana-plugin/src/pages/schedules/Schedules.tsx | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx index c9c2d7f48e..4f903d661f 100644 --- a/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx +++ b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx @@ -1,6 +1,7 @@ -import { Tooltip } from '@grafana/ui'; import React, { FC } from 'react'; +import { Tooltip } from '@grafana/ui'; + interface MediaMatchTooltipProps { placement: 'top' | 'bottom' | 'right' | 'left'; content: string; diff --git a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx index 8aee0b44cc..a91a411774 100644 --- a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx +++ b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx @@ -5,9 +5,8 @@ import cn from 'classnames/bind'; import { ScheduleType } from 'models/schedule/schedule.types'; -import { SchedulesFiltersType } from './SchedulesFilters.types'; - import styles from './SchedulesFilters.module.scss'; +import { SchedulesFiltersType } from './SchedulesFilters.types'; const cx = cn.bind(styles); diff --git a/grafana-plugin/src/pages/incident/Incident.helpers.tsx b/grafana-plugin/src/pages/incident/Incident.helpers.tsx index aa935b2415..be27ae5720 100644 --- a/grafana-plugin/src/pages/incident/Incident.helpers.tsx +++ b/grafana-plugin/src/pages/incident/Incident.helpers.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { Button, HorizontalGroup, IconButton, Tooltip, VerticalGroup } from '@grafana/ui'; import Avatar from 'components/Avatar/Avatar'; +import { MatchMediaTooltip } from 'components/MatchMediaTooltip/MatchMediaTooltip'; import PluginLink from 'components/PluginLink/PluginLink'; import Tag from 'components/Tag/Tag'; import Text from 'components/Text/Text'; @@ -13,7 +14,6 @@ import { User } from 'models/user/user.types'; import SilenceCascadingSelect from 'pages/incidents/parts/SilenceCascadingSelect'; import { move } from 'state/helpers'; import { UserActions } from 'utils/authorization'; -import { MatchMediaTooltip } from 'components/MatchMediaTooltip/MatchMediaTooltip'; import { TABLE_COLUMN_MAX_WIDTH } from 'utils/consts'; export function getIncidentStatusTag(alert: Alert) { diff --git a/grafana-plugin/src/pages/incidents/Incidents.tsx b/grafana-plugin/src/pages/incidents/Incidents.tsx index 92db67879a..e14334973a 100644 --- a/grafana-plugin/src/pages/incidents/Incidents.tsx +++ b/grafana-plugin/src/pages/incidents/Incidents.tsx @@ -24,11 +24,10 @@ import { withMobXProviderContext } from 'state/withStore'; import LocationHelper from 'utils/LocationHelper'; import { UserActions } from 'utils/authorization'; +import styles from './Incidents.module.scss'; import { IncidentDropdown } from './parts/IncidentDropdown'; import SilenceCascadingSelect from './parts/SilenceCascadingSelect'; -import styles from './Incidents.module.scss'; - const cx = cn.bind(styles); interface Pagination { diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index 15dc6c1bee..48f56d0c9d 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -8,6 +8,7 @@ import { observer } from 'mobx-react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; import Avatar from 'components/Avatar/Avatar'; +import { MatchMediaTooltip } from 'components/MatchMediaTooltip/MatchMediaTooltip'; import NewScheduleSelector from 'components/NewScheduleSelector/NewScheduleSelector'; import PluginLink from 'components/PluginLink/PluginLink'; import ScheduleCounter from 'components/ScheduleCounter/ScheduleCounter'; @@ -32,7 +33,6 @@ import { UserActions } from 'utils/authorization'; import { PLUGIN_ROOT, TABLE_COLUMN_MAX_WIDTH } from 'utils/consts'; import styles from './Schedules.module.css'; -import { MatchMediaTooltip } from 'components/MatchMediaTooltip/MatchMediaTooltip'; const cx = cn.bind(styles); From 107d5bf6bb256a2b1e777325ae892c511e352f0c Mon Sep 17 00:00:00 2001 From: teodosii Date: Fri, 27 Jan 2023 13:27:10 +0200 Subject: [PATCH 13/30] u-disabled --- grafana-plugin/src/style/utils.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/grafana-plugin/src/style/utils.css b/grafana-plugin/src/style/utils.css index e9322226ef..fd5afa404f 100644 --- a/grafana-plugin/src/style/utils.css +++ b/grafana-plugin/src/style/utils.css @@ -50,5 +50,6 @@ .u-disabled { opacity: var(--opacity); - cursor: not-allowed; + cursor: not-allowed !important; + pointer-events: none; } From 5a5aefd5f228847b470046665f1da0898499cc53 Mon Sep 17 00:00:00 2001 From: teodosii Date: Mon, 30 Jan 2023 11:21:43 +0200 Subject: [PATCH 14/30] debounce matchMediaTooltip, added changelog --- CHANGELOG.md | 2 + .../MatchMediaTooltip/MatchMediaTooltip.tsx | 42 ++++++++++++++----- .../AlertRules/AlertRules.module.css | 2 + 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42865e2171..8e142a42aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Improve logging for creating contact point for Grafana Alerting integration +- Incidents - Removed buttons column and replaced status with toggler ([#1237](https://github.com/grafana/oncall/issues/1237)) +- Responsiveness changes across multiple pages (Incidents, Integrations, Schedules) ([#1237](https://github.com/grafana/oncall/issues/1237)) ### Fixed diff --git a/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx index 4f903d661f..b617c2d082 100644 --- a/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx +++ b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx @@ -1,8 +1,9 @@ -import React, { FC } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import { Tooltip } from '@grafana/ui'; +import { debounce } from 'throttle-debounce'; -interface MediaMatchTooltipProps { +interface MatchMediaTooltipProps { placement: 'top' | 'bottom' | 'right' | 'left'; content: string; children: JSX.Element; @@ -11,19 +12,22 @@ interface MediaMatchTooltipProps { minWidth?: number; } -export const MatchMediaTooltip: FC = ({ minWidth, maxWidth, placement, content, children }) => { - let match: MediaQueryList; +const DEBOUNCE_MS = 200; - if (minWidth && maxWidth) { - match = window.matchMedia(`(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`); - } else if (minWidth) { - match = window.matchMedia(`(min-width: ${minWidth}px)`); - } else if (maxWidth) { - match = window.matchMedia(`(max-width: ${maxWidth}px)`); - } else { +export const MatchMediaTooltip: FC = ({ minWidth, maxWidth, placement, content, children }) => { + const [match, setMatch] = useState(getMatch()); + if (!match) { return <>{children}; } + useEffect(() => { + const debouncedResize = debounce(DEBOUNCE_MS, onWindowResize); + window.addEventListener('resize', debouncedResize); + return () => { + window.removeEventListener('resize', debouncedResize); + }; + }, []); + if (match.matches) { return ( @@ -33,4 +37,20 @@ export const MatchMediaTooltip: FC = ({ minWidth, maxWid } return <>{children}; + + function onWindowResize() { + setMatch(getMatch()); + } + + function getMatch() { + if (minWidth && maxWidth) { + return window.matchMedia(`(min-width: ${minWidth}px) and (max-width: ${maxWidth}px)`); + } else if (minWidth) { + return window.matchMedia(`(min-width: ${minWidth}px)`); + } else if (maxWidth) { + return window.matchMedia(`(max-width: ${maxWidth}px)`); + } + + return undefined; + } }; diff --git a/grafana-plugin/src/containers/AlertRules/AlertRules.module.css b/grafana-plugin/src/containers/AlertRules/AlertRules.module.css index 37c913502e..cb6bfe724e 100644 --- a/grafana-plugin/src/containers/AlertRules/AlertRules.module.css +++ b/grafana-plugin/src/containers/AlertRules/AlertRules.module.css @@ -49,6 +49,8 @@ display: flex; justify-content: space-between; align-items: center; + flex-wrap: wrap; + gap: 8px; } .channel-filter-header-title { From 3040e76acee4967f8c794dd1dc206d70fbea6d40 Mon Sep 17 00:00:00 2001 From: teodosii Date: Mon, 30 Jan 2023 11:28:48 +0200 Subject: [PATCH 15/30] hook change to make linter happy --- .../src/components/MatchMediaTooltip/MatchMediaTooltip.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx index b617c2d082..6199712cb6 100644 --- a/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx +++ b/grafana-plugin/src/components/MatchMediaTooltip/MatchMediaTooltip.tsx @@ -16,9 +16,6 @@ const DEBOUNCE_MS = 200; export const MatchMediaTooltip: FC = ({ minWidth, maxWidth, placement, content, children }) => { const [match, setMatch] = useState(getMatch()); - if (!match) { - return <>{children}; - } useEffect(() => { const debouncedResize = debounce(DEBOUNCE_MS, onWindowResize); @@ -28,7 +25,7 @@ export const MatchMediaTooltip: FC = ({ minWidth, maxWid }; }, []); - if (match.matches) { + if (match?.matches) { return ( {children} From c08394e019515ab34ae91ba5f9ba77dff6647deb Mon Sep 17 00:00:00 2001 From: teodosii Date: Mon, 30 Jan 2023 13:23:52 +0200 Subject: [PATCH 16/30] export --- grafana-plugin/src/pages/incident/Incident.helpers.tsx | 2 +- grafana-plugin/src/pages/incidents/Incidents.tsx | 2 +- .../src/pages/incidents/parts/IncidentDropdown.tsx | 2 +- .../src/pages/incidents/parts/SilenceCascadingSelect.tsx | 5 +---- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/grafana-plugin/src/pages/incident/Incident.helpers.tsx b/grafana-plugin/src/pages/incident/Incident.helpers.tsx index be27ae5720..c71c33e3ec 100644 --- a/grafana-plugin/src/pages/incident/Incident.helpers.tsx +++ b/grafana-plugin/src/pages/incident/Incident.helpers.tsx @@ -11,7 +11,7 @@ import { WithPermissionControl } from 'containers/WithPermissionControl/WithPerm import { MaintenanceIntegration } from 'models/alert_receive_channel'; import { Alert as AlertType, Alert, IncidentStatus } from 'models/alertgroup/alertgroup.types'; import { User } from 'models/user/user.types'; -import SilenceCascadingSelect from 'pages/incidents/parts/SilenceCascadingSelect'; +import { SilenceCascadingSelect } from 'pages/incidents/parts/SilenceCascadingSelect'; import { move } from 'state/helpers'; import { UserActions } from 'utils/authorization'; import { TABLE_COLUMN_MAX_WIDTH } from 'utils/consts'; diff --git a/grafana-plugin/src/pages/incidents/Incidents.tsx b/grafana-plugin/src/pages/incidents/Incidents.tsx index e14334973a..3c32494633 100644 --- a/grafana-plugin/src/pages/incidents/Incidents.tsx +++ b/grafana-plugin/src/pages/incidents/Incidents.tsx @@ -26,7 +26,7 @@ import { UserActions } from 'utils/authorization'; import styles from './Incidents.module.scss'; import { IncidentDropdown } from './parts/IncidentDropdown'; -import SilenceCascadingSelect from './parts/SilenceCascadingSelect'; +import { SilenceCascadingSelect } from './parts/SilenceCascadingSelect'; const cx = cn.bind(styles); diff --git a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx index a3bb7c53a3..4eb6643d4a 100644 --- a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx +++ b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx @@ -11,7 +11,7 @@ import { Alert, AlertAction, IncidentStatus } from 'models/alertgroup/alertgroup import styles from 'pages/incidents/parts/IncidentDropdown.module.scss'; import { UserActions } from 'utils/authorization'; -import SilenceCascadingSelect from './SilenceCascadingSelect'; +import { SilenceCascadingSelect } from './SilenceCascadingSelect'; const cx = cn.bind(styles); diff --git a/grafana-plugin/src/pages/incidents/parts/SilenceCascadingSelect.tsx b/grafana-plugin/src/pages/incidents/parts/SilenceCascadingSelect.tsx index db122ea1ac..c92ac18f3a 100644 --- a/grafana-plugin/src/pages/incidents/parts/SilenceCascadingSelect.tsx +++ b/grafana-plugin/src/pages/incidents/parts/SilenceCascadingSelect.tsx @@ -17,7 +17,7 @@ interface SilenceCascadingSelectProps { onSelect: (value: number) => void; } -const SilenceCascadingSelect = observer((props: SilenceCascadingSelectProps) => { +export const SilenceCascadingSelect = observer((props: SilenceCascadingSelectProps) => { const { onSelect, isCascading = true, className, disabled = false, buttonSize } = props; const store = useStore(); @@ -49,7 +49,6 @@ const SilenceCascadingSelect = observer((props: SilenceCascadingSelectProps) => } function renderAsSelectDropdown() { - console.log('render'); return ( onSelect(Number(value))} - options={getOptions()} - /> - ); - } + + ); function getOptions() { return silenceOptions.map((silenceOption: SelectOption) => ({ diff --git a/grafana-plugin/src/pages/incidents/parts/SilenceSelect.tsx b/grafana-plugin/src/pages/incidents/parts/SilenceSelect.tsx new file mode 100644 index 0000000000..c0e10845a8 --- /dev/null +++ b/grafana-plugin/src/pages/incidents/parts/SilenceSelect.tsx @@ -0,0 +1,44 @@ +import React from 'react'; + +import { Select } from '@grafana/ui'; +import { observer } from 'mobx-react'; + +import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl'; +import { SelectOption } from 'state/types'; +import { useStore } from 'state/useStore'; +import { UserActions } from 'utils/authorization'; + +interface SilenceSelectProps { + placeholder?: string; + + onSelect: (value: number) => void; +} + +export const SilenceSelect = observer((props: SilenceSelectProps) => { + const { placeholder = 'Silence for', onSelect } = props; + + const store = useStore(); + + const { alertGroupStore } = store; + + const silenceOptions = alertGroupStore.silenceOptions || []; + + return ( + + ( iOS - + Coming Soon diff --git a/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx b/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx index 6a7b4ef7ed..88b0f83e6c 100644 --- a/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx +++ b/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx @@ -50,7 +50,7 @@ const ScheduleOverrideForm: FC = (props) => { shiftId, startMoment, shiftMoment = dayjs().startOf('day').add(1, 'day'), - shiftColor = '#C69B06', + shiftColor = getComputedStyle(document.documentElement).getPropertyValue('--tag-warning'), } = props; const store = useStore(); diff --git a/grafana-plugin/src/pages/incident/Incident.helpers.tsx b/grafana-plugin/src/pages/incident/Incident.helpers.tsx index c4841900fd..8ce21c9daf 100644 --- a/grafana-plugin/src/pages/incident/Incident.helpers.tsx +++ b/grafana-plugin/src/pages/incident/Incident.helpers.tsx @@ -14,13 +14,13 @@ import { User } from 'models/user/user.types'; import { SilenceButtonCascader } from 'pages/incidents/parts/SilenceButtonCascader'; import { move } from 'state/helpers'; import { UserActions } from 'utils/authorization'; -import { COLOR_DANGER, COLOR_PRIMARY, COLOR_SECONDARY, COLOR_WARNING } from 'utils/consts'; +import { TABLE_COLUMN_MAX_WIDTH } from 'utils/consts'; export function getIncidentStatusTag(alert: Alert) { switch (alert.status) { case IncidentStatus.Firing: return ( - + Firing @@ -28,7 +28,7 @@ export function getIncidentStatusTag(alert: Alert) { ); case IncidentStatus.Acknowledged: return ( - + Acknowledged @@ -36,7 +36,7 @@ export function getIncidentStatusTag(alert: Alert) { ); case IncidentStatus.Resolved: return ( - + Resolved @@ -44,7 +44,7 @@ export function getIncidentStatusTag(alert: Alert) { ); case IncidentStatus.Silenced: return ( - + Silenced diff --git a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx index b6897e52a1..59b552e890 100644 --- a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx +++ b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.tsx @@ -17,15 +17,15 @@ const cx = cn.bind(styles); const getIncidentTagColor = (alert: Alert) => { if (alert.status === IncidentStatus.Resolved) { - return '#299C46'; + return getComputedStyle(document.documentElement).getPropertyValue('--tag-primary'); } if (alert.status === IncidentStatus.Firing) { - return '#E02F44'; + return getComputedStyle(document.documentElement).getPropertyValue('--tag-danger'); } if (alert.status === IncidentStatus.Acknowledged) { - return '#C69B06'; + return getComputedStyle(document.documentElement).getPropertyValue('--tag-warning'); } - return '#464C54'; + return getComputedStyle(document.documentElement).getPropertyValue('--tag-secondary'); }; function ListMenu({ alert, openMenu }: { alert: Alert; openMenu: React.MouseEventHandler }) { diff --git a/grafana-plugin/src/style/vars.css b/grafana-plugin/src/style/vars.css index 53a3181b4e..1f0d20f31d 100644 --- a/grafana-plugin/src/style/vars.css +++ b/grafana-plugin/src/style/vars.css @@ -16,6 +16,12 @@ --always-gray: #ccccdc; --title-marginBottom: 16px; --opacity: 0.5; + + /* These seem to slightly differ from warning/success/error colors from below */ + --tag-danger: #e02f44; + --tag-warning: #c69b06; + --tag-primary: #299c46; + --tag-secondary: #464c54; } .theme-light { diff --git a/grafana-plugin/src/utils/consts.ts b/grafana-plugin/src/utils/consts.ts index 095b76aecd..57e004ba6f 100644 --- a/grafana-plugin/src/utils/consts.ts +++ b/grafana-plugin/src/utils/consts.ts @@ -33,8 +33,3 @@ export const DOCS_TELEGRAM_SETUP = 'https://grafana.com/docs/grafana-cloud/oncal // Make sure if you chage max-width here you also change it in responsive.css export const TABLE_COLUMN_MAX_WIDTH = 1500; - -export const COLOR_DANGER = '#E02F44'; -export const COLOR_WARNING = '#C69B06'; -export const COLOR_PRIMARY = '#299C46'; -export const COLOR_SECONDARY = '#464C54'; From 68dbab5660f06b790b59f5be8e5fd21f1d06dfdd Mon Sep 17 00:00:00 2001 From: teodosii Date: Mon, 6 Feb 2023 16:10:12 +0200 Subject: [PATCH 25/30] ux/ui suggestions from Raphael --- .../SchedulesFilters/SchedulesFilters.module.scss | 13 ++++++------- .../SchedulesFilters/SchedulesFilters.tsx | 4 ++-- .../src/pages/schedules/Schedules.module.css | 11 ++++++----- grafana-plugin/src/pages/schedules/Schedules.tsx | 6 ++---- grafana-plugin/src/style/global.css | 5 +++++ 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.scss b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.scss index 311b17ba69..0d35e21d66 100644 --- a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.scss +++ b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.module.scss @@ -1,14 +1,13 @@ -.root { +.right { display: flex; - flex-direction: row; flex-wrap: wrap; row-gap: 4px; column-gap: 8px; } -.right { - display: flex; - flex-wrap: wrap; - row-gap: 4px; - column-gap: 8px; +@media screen and (max-width: 1600px) { + .right { + order: 3; + width: 100%; + } } diff --git a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx index a91a411774..25fb810bd8 100644 --- a/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx +++ b/grafana-plugin/src/components/SchedulesFilters/SchedulesFilters.tsx @@ -39,7 +39,7 @@ const SchedulesFilters = (props: SchedulesFiltersProps) => { ); return ( -
+ <>
{ />
-
+ ); }; diff --git a/grafana-plugin/src/pages/schedules/Schedules.module.css b/grafana-plugin/src/pages/schedules/Schedules.module.css index 4bc767e5e5..bd5b3bc90f 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.module.css +++ b/grafana-plugin/src/pages/schedules/Schedules.module.css @@ -14,16 +14,17 @@ .schedules__filters-container { display: flex; + flex-direction: row; + flex-wrap: wrap; + row-gap: 4px; + column-gap: 8px; width: 100%; } -.schedules__filters-container-left { - flex-grow: 1; -} - -.schedules__filters-container-right { +.schedules__actions { display: flex; justify-content: flex-end; + flex-grow: 1; gap: 8px; padding-top: 19px; } diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index 2ce7eb6715..00c00dcc6e 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -139,10 +139,8 @@ class SchedulesPage extends React.Component
-
- -
-
+ +
{users && ( Date: Mon, 6 Feb 2023 16:21:14 +0200 Subject: [PATCH 26/30] snapshots updated --- .../__snapshots__/MobileAppConnection.test.tsx.snap | 7 ------- .../__snapshots__/DownloadIcons.test.tsx.snap | 1 - 2 files changed, 8 deletions(-) diff --git a/grafana-plugin/src/containers/MobileAppConnection/__snapshots__/MobileAppConnection.test.tsx.snap b/grafana-plugin/src/containers/MobileAppConnection/__snapshots__/MobileAppConnection.test.tsx.snap index b49ac43b66..3830a8e01a 100644 --- a/grafana-plugin/src/containers/MobileAppConnection/__snapshots__/MobileAppConnection.test.tsx.snap +++ b/grafana-plugin/src/containers/MobileAppConnection/__snapshots__/MobileAppConnection.test.tsx.snap @@ -80,7 +80,6 @@ exports[`MobileAppConnection if we disconnect the app, it disconnects and fetche Coming Soon @@ -2424,7 +2423,6 @@ exports[`MobileAppConnection it shows a QR code if the app isn't already connect Coming Soon @@ -2536,7 +2534,6 @@ exports[`MobileAppConnection it shows a loading message if it is currently disco Coming Soon @@ -2648,7 +2645,6 @@ exports[`MobileAppConnection it shows a loading message if it is currently fetch Coming Soon @@ -2760,7 +2756,6 @@ exports[`MobileAppConnection it shows a message when the mobile app is already c Coming Soon @@ -2972,7 +2967,6 @@ exports[`MobileAppConnection it shows an error message if there was an error dis Coming Soon @@ -3075,7 +3069,6 @@ exports[`MobileAppConnection it shows an error message if there was an error fet Coming Soon diff --git a/grafana-plugin/src/containers/MobileAppConnection/parts/DownloadIcons/__snapshots__/DownloadIcons.test.tsx.snap b/grafana-plugin/src/containers/MobileAppConnection/parts/DownloadIcons/__snapshots__/DownloadIcons.test.tsx.snap index d021d2eedd..762bfdae48 100644 --- a/grafana-plugin/src/containers/MobileAppConnection/parts/DownloadIcons/__snapshots__/DownloadIcons.test.tsx.snap +++ b/grafana-plugin/src/containers/MobileAppConnection/parts/DownloadIcons/__snapshots__/DownloadIcons.test.tsx.snap @@ -74,7 +74,6 @@ exports[`DownloadIcons it renders properly 1`] = ` Coming Soon From 3bc4b5cff41a5aaaaddf26d68fc11f2e1227191d Mon Sep 17 00:00:00 2001 From: teodosii Date: Tue, 7 Feb 2023 11:23:58 +0200 Subject: [PATCH 27/30] hide on scroll --- .../components/WithContextMenu/WithContextMenu.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx b/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx index 2921a655ef..cd1b154429 100644 --- a/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx +++ b/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx @@ -9,6 +9,8 @@ export interface WithContextMenuProps { focusOnOpen?: boolean; } +const query = '[class$="-page-container"] .scrollbar-view'; + export const WithContextMenu: React.FC = ({ children, renderMenuItems, @@ -22,6 +24,15 @@ export const WithContextMenu: React.FC = ({ setIsMenuOpen(forceIsOpen); }, [forceIsOpen]); + useEffect(() => { + const onScrollFn = () => setIsMenuOpen(false); + document.querySelector(query)?.addEventListener('scroll', onScrollFn); + + return () => { + document.querySelector(query)?.removeEventListener('scroll', onScrollFn); + }; + }, []); + return ( <> {children({ From d9b5717ec4e7b037442ab895ce8270cadbc48440 Mon Sep 17 00:00:00 2001 From: teodosii Date: Tue, 7 Feb 2023 11:26:37 +0200 Subject: [PATCH 28/30] changelog --- CHANGELOG.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f9449efa3..8dd1f076d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## v1.1.24 (2023-02-06) +## Unreleased ### Fixed - Design polishing ([1290](https://github.com/grafana/oncall/pull/1290)) - Not showing contact details in User tooltip if User does not have edit/admin access +### Changes + +- Incidents - Removed buttons column and replaced status with toggler ([#1237](https://github.com/grafana/oncall/issues/1237)) +- Responsiveness changes across multiple pages (Incidents, Integrations, Schedules) ([#1237](https://github.com/grafana/oncall/issues/1237)) + ## v1.1.23 (2023-02-06) ### Fixed @@ -54,8 +59,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Improve logging for creating contact point for Grafana Alerting integration -- Incidents - Removed buttons column and replaced status with toggler ([#1237](https://github.com/grafana/oncall/issues/1237)) -- Responsiveness changes across multiple pages (Incidents, Integrations, Schedules) ([#1237](https://github.com/grafana/oncall/issues/1237)) ### Fixed From 8db1c5e0c08ce66c7aa34cefc87658aae36c74b1 Mon Sep 17 00:00:00 2001 From: teodosii Date: Tue, 7 Feb 2023 11:39:07 +0200 Subject: [PATCH 29/30] same for window resize --- .../src/components/WithContextMenu/WithContextMenu.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx b/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx index cd1b154429..c28a213c1a 100644 --- a/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx +++ b/grafana-plugin/src/components/WithContextMenu/WithContextMenu.tsx @@ -25,11 +25,13 @@ export const WithContextMenu: React.FC = ({ }, [forceIsOpen]); useEffect(() => { - const onScrollFn = () => setIsMenuOpen(false); - document.querySelector(query)?.addEventListener('scroll', onScrollFn); + const onScrollOrResizeFn = () => setIsMenuOpen(false); + document.querySelector(query)?.addEventListener('scroll', onScrollOrResizeFn); + window.addEventListener('resize', onScrollOrResizeFn); return () => { - document.querySelector(query)?.removeEventListener('scroll', onScrollFn); + document.querySelector(query)?.removeEventListener('scroll', onScrollOrResizeFn); + window.removeEventListener('resize', onScrollOrResizeFn); }; }, []); From 2c097f095b74468ada0555991131dd9d7d164c6c Mon Sep 17 00:00:00 2001 From: teodosii Date: Tue, 7 Feb 2023 13:21:14 +0200 Subject: [PATCH 30/30] css var for warning --- .../src/pages/incidents/parts/IncidentDropdown.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.module.scss b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.module.scss index 9e2cb9be39..3cb0a70fd5 100644 --- a/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.module.scss +++ b/grafana-plugin/src/pages/incidents/parts/IncidentDropdown.module.scss @@ -34,7 +34,7 @@ } &--acknowledge { - color: var(--warning-text-color); + color: var(--tag-warning); } &--firing { color: var(--error-text-color);