From 22b21bedabfa49df31a48ad8f10de588bcf1f142 Mon Sep 17 00:00:00 2001 From: Isa Ozler Date: Sun, 10 Sep 2023 22:12:48 +0200 Subject: [PATCH] minor bugfixes --- CHANGELOG.md | 8 ++++ src/ShiftSelector.tsx | 55 ++++++++++++++++++++++++- src/components/inputWrappers.tsx | 1 - src/components/options.tsx | 18 ++++---- src/components/progressBar.tsx | 36 ++++++++++++++++ src/hooks/core.ts | 71 ++++++++++++++++++++------------ src/module.ts | 59 +++++++++++++++++++++++++- src/types.ts | 5 ++- src/utils.ts | 27 ++++++++---- 9 files changed, 233 insertions(+), 47 deletions(-) create mode 100644 src/components/progressBar.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bac41d..131f59b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.1.2 + +- Fixes for [issue #16](https://github.com/isaozler/grafana-shift-selector/issues/16) + - Fixed groupUUID field. Gets auto-selected if not provided. + - Breaking refresh rate fixed. Added custom refresh rate input to force dashboard refresh at preferred rate. +- Added progressbar to visualise the refresh rate +- Deprecated location service replaced + ## 0.1.1 - Fixed review bugs diff --git a/src/ShiftSelector.tsx b/src/ShiftSelector.tsx index 2ff3998..3f34ffb 100644 --- a/src/ShiftSelector.tsx +++ b/src/ShiftSelector.tsx @@ -4,23 +4,31 @@ import './styles/core.css'; import 'react-datepicker/dist/react-datepicker.min.css'; import { PanelProps } from '@grafana/data'; -import { TPropOptions } from './types'; +import { TPropOptions, vars } from './types'; import { setTypeChangeHandler, shiftSelectHandler } from './utils'; import { ShiftSelectorWrapper, ShiftSelectorContainer } from './styles/components'; import { ShiftOptions } from './components/options'; import { InputWrappers } from './components/inputWrappers'; import { Alerts } from './components/alerts'; import { useShiftSelectorHook } from './hooks/core'; +import { locationService } from '@grafana/runtime'; +import { ProgressBar } from './components/progressBar'; + +let refreshT: NodeJS.Timer | null = null; const ShiftSelector: React.FC> = (props) => { + const [renderCount, setRenderCount] = useState(0); const [isBlockedRender, setIsBlockedRender] = useState(false); const [shiftSelectorPluginPanel, setShiftSelectorPluginPanel] = useState | null>(null); + const [autoSelectShiftGroup, setAutoSelectShiftGroup] = useState( + locationService.getSearch().get(vars.queryShiftsGroup) ?? props.options.autoSelectShiftGroup + ); const { data: _data, width, height, timeRange } = props; + const { isShowDayLabel, isShowTimeLabel, isAutoSelectShift, - autoSelectShiftGroup, dayLabel, rangeLabelType, shiftOptionsLabelType, @@ -30,7 +38,9 @@ const ShiftSelector: React.FC> = (props) => { var_label_mapping, isShowRangeButtons, isShowProductionDateSelector, + isProgressbarVisible, } = props.options; + const { resetAlert, @@ -50,6 +60,10 @@ const ShiftSelector: React.FC> = (props) => { setProductionDate, } = useShiftSelectorHook({ ...props, + options: { + ...props.options, + autoSelectShiftGroup, + }, shiftSelectorPluginPanel, } as PanelProps); @@ -86,12 +100,46 @@ const ShiftSelector: React.FC> = (props) => { } }, [shiftSelectorRef, setAlerts]); + useEffect(() => { + if ( + !!props.options.autoSelectShiftGroup && + !!autoSelectShiftGroup && + props.options.autoSelectShiftGroup !== autoSelectShiftGroup + ) { + locationService.partial( + { + [vars.queryShiftsGroup]: props.options.autoSelectShiftGroup, + [vars.queryShiftsOptions]: null, + }, + true + ); + setAutoSelectShiftGroup(props.options.autoSelectShiftGroup); + } + }, [props.options.autoSelectShiftGroup, autoSelectShiftGroup]); + + useEffect(() => { + if (refreshT) { + clearInterval(refreshT); + refreshT = null; + } + + if (props.options._refreshInterval) { + refreshT = setInterval(() => { + props.eventBus.publish({ type: 'refresh', payload: undefined, origin: undefined }); + setRenderCount((d) => d + 1); + }, props.options._refreshInterval as unknown as number); + } + }, [props.eventBus, props.options._refreshInterval]); + if (isBlockedRender) { return ; } return ( + {isProgressbarVisible && isAutoSelectShift && ( + + )} {shiftOptions?.options?.length ? ( > = (props) => { > {!isAutoSelectShift ? ( > = (props) => { setProductionDate={setProductionDate} isShowProductionDateSelector={isShowProductionDateSelector} isShowRangeButtons={isShowRangeButtons} + isProgressbarVisible={isProgressbarVisible} /> ) : ( <> diff --git a/src/components/inputWrappers.tsx b/src/components/inputWrappers.tsx index 46ca493..7cea9a1 100644 --- a/src/components/inputWrappers.tsx +++ b/src/components/inputWrappers.tsx @@ -16,7 +16,6 @@ type TPropInputWrapperOptions = Omit< | 'autoSelectShiftGroup' | 'isShowTimeLabel' | 'shiftOptionsLabelType' - | 'refreshInterval' | 'var_query_map_dynamic' | 'var_query_map_static' | 'var_label_mapping' diff --git a/src/components/options.tsx b/src/components/options.tsx index d62268f..30b07d5 100644 --- a/src/components/options.tsx +++ b/src/components/options.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { config } from '@grafana/runtime'; +import { config, locationService } from '@grafana/runtime'; import { ShiftOptionsWrapper, ShiftsWrapper, @@ -68,9 +68,9 @@ export const ShiftOptions = ({ const { sunny, sunset, night } = mappingsParsed; const allMappings = { - sunny: ['morning', 'morgen', 'day', ...(sunny ? sunny : [])], - 'sunset-down': ['afternoon', 'middag', ...(sunset ? sunset : [])], - night: ['night', 'nacht', ...(night ? night : [])], + sunny: ['morning', 'morgen', 'day', ...(sunny || [])], + 'sunset-down': ['afternoon', 'middag', ...(sunset || [])], + night: ['night', 'nacht', ...(night || [])], }; return ( @@ -99,8 +99,8 @@ export const ShiftOptions = ({ const start = `${sh}:${sm}`; const end = `${eh}:${em}`; const isActive = - new URLSearchParams(window.location.search).get(vars.queryShiftsOptions) === uuid || - new URLSearchParams(window.location.search).get(vars.queryShiftsOptions) === 'All'; + locationService.getSearch().get(vars.queryShiftsOptions) === uuid || + locationService.getSearch().get(vars.queryShiftsOptions) === 'All'; const fromTimeLabel = setType === 'from' ? `${start}` : `${end}`; const timeLabel = setType === 'both' ? `${start} - ${end}` : fromTimeLabel; @@ -114,7 +114,11 @@ export const ShiftOptions = ({ ? `mdi mdi-weather-${buttonTypes(label, allMappings)}` : '' }`} - onClick={() => shiftSelectHandler(item, setShiftParams, productionDate)} + onClick={() => { + if (!isRealtimeActive) { + shiftSelectHandler(item, setShiftParams, productionDate); + } + }} isRealtime={isRealtimeActive} > diff --git a/src/components/progressBar.tsx b/src/components/progressBar.tsx new file mode 100644 index 0000000..039aad1 --- /dev/null +++ b/src/components/progressBar.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +import { config } from '@grafana/runtime'; +import { keyframes, styled } from '@stitches/react'; + +const colors: any = config.theme.colors; + +const load = keyframes({ + '0%': { width: '0%' }, + '100%': { width: '100%' }, +}); + +export const Bar = styled('div', { + width: '100%', + height: 4, + marginTop: -16, + marginBottom: 12, + position: 'relative', + '&:after': { + content: '', + position: 'absolute', + top: 0, + left: 0, + background: colors.border1, + width: 10, + height: '100%', + animationName: load, + animationDuration: 'inherit', + animationTimingFunction: 'linear', + animationIterationCount: 'infinite', + }, +}); + +export const ProgressBar = ({ refresh, renderCount }: { refresh: string; renderCount: number }) => { + return ; +}; diff --git a/src/hooks/core.ts b/src/hooks/core.ts index 0c3bc6b..9184060 100644 --- a/src/hooks/core.ts +++ b/src/hooks/core.ts @@ -8,9 +8,9 @@ import { getDataSourceSrv, toDataQueryResponse, RefreshEvent, - getLocationSrv, getTemplateSrv, TemplateSrv, + locationService, } from '@grafana/runtime'; import { @@ -25,21 +25,21 @@ import { TStaticShift, vars, } from '../types'; -import { dateTimeFormat, getRelativeDates, startHourIsGreater, transformShiftData, updateActiveShift } from '../utils'; +import { dateTimeFormat, getInitGroupUUID, getRelativeDates, startHourIsGreater, transformShiftData, updateActiveShift } from '../utils'; + +let isInitiated = false; export const useShiftSelectorHook = (props: PanelProps) => { const { data: _data, width, height, timeRange, eventBus } = props; const { isAutoSelectShift, - autoSelectShiftGroup, - refreshInterval, isDataSourceShifts, var_query_map_dynamic, var_query_map_static, shiftSelectorPluginPanel, } = props.options; - const locationSrv = getLocationSrv(); + const locationSrv = locationService; const templateSrv = getTemplateSrv() as TemplateSrv & { timeRange: TimeRange }; const dateRange = templateSrv.timeRange; @@ -55,6 +55,7 @@ export const useShiftSelectorHook = (props: PanelProps) => { const [siteUUID, setSiteUUID] = useState(); const [isStatic, setIsStatic] = useState(false); const [sqlConfig, setSqlConfig] = useState(null); + const [autoSelectShiftGroup, setAutoSelectShiftGroup] = useState(new URLSearchParams(window.location.search).get(vars.queryShiftsGroup) ?? props.options.autoSelectShiftGroup); const processShifts = useCallback(({ rowsCount, responseFields }) => { return Array.from({ length: rowsCount }) @@ -120,14 +121,13 @@ export const useShiftSelectorHook = (props: PanelProps) => { }, [alerts] ); + const getRefreshRate = useCallback(() => { - const refreshValue = new URLSearchParams(window.location.search).get('refresh'); - const refresh = { - ...(refreshValue ? { refresh: refreshValue } : isAutoSelectShift ? { refresh: refreshInterval } : {}), - }; + return { + refresh: locationService.getSearch().get('refresh'), + } + }, []); - return refresh; - }, [isAutoSelectShift, refreshInterval]); const setShiftParams = useCallback( (shift: TExtendedShift, isManualUpdate = false) => { const { startDate, endDate } = shift || {}; @@ -340,16 +340,14 @@ ORDER by ??, ?? const query = { from: isSwapDates ? to : from, to: isSwapDates ? from : to, + [vars.queryShiftsGroup]: autoSelectShiftGroup, [vars.queryShiftsOptions]: uuid, ...getRefreshRate(), }; - locationSrv.update({ - partial: true, - query, - }); + locationSrv.partial(query, false); } - }, [locationSrv, customTimeRange, timeRange.to, timeRange.from, getRefreshRate, setInitDateRange]); + }, [locationSrv, customTimeRange, timeRange.to, timeRange.from, getRefreshRate, setInitDateRange, autoSelectShiftGroup, isAutoSelectShift]); useEffect(() => { if (width < 400) { @@ -419,15 +417,6 @@ ORDER by ??, ?? } }, [siteUUID, sqlConfig, getValues]); - useEffect(() => { - locationSrv.update({ - partial: true, - query: { - ...getRefreshRate(), - }, - }); - }, [locationSrv, getRefreshRate]); - useEffect(() => { if (isStatic && sqlConfig?.static?.shifts) { setShiftOptions(() => processStaticOptions(sqlConfig.static?.shifts)); @@ -439,9 +428,35 @@ ORDER by ??, ?? }, [isStatic, sqlConfig, setShiftOptions, processStaticOptions, templateSrv]); useEffect(() => { - const subscriber = eventBus.getStream(RefreshEvent).subscribe((event) => { - const isRealtimeActive = !!(isAutoSelectShift && autoSelectShiftGroup); + const isRealtimeActive = !!(isAutoSelectShift && autoSelectShiftGroup); + + if (isAutoSelectShift && !autoSelectShiftGroup && shiftOptions?.options?.length && shiftValues?.length) { + const initGroup = getInitGroupUUID(shiftOptions.options, shiftValues) + + locationSrv.partial({ + [vars.queryShiftsGroup]: initGroup, + ...getRefreshRate(), + }, false); + setAutoSelectShiftGroup(initGroup) + } + if (!isInitiated && isRealtimeActive && autoSelectShiftGroup && shiftOptions?.options?.length && shiftValues?.length && productionDate) { + isInitiated = true; + + updateActiveShift({ + setShiftParams, + autoSelectShiftGroup, + isAutoSelectShift, + shifts: { + options: shiftOptions.options, + values: shiftValues, + }, + setProductionDate, + productionDate, + }); + } + + const subscriber = eventBus.getStream(RefreshEvent).subscribe((event) => { if (isRealtimeActive) { updateActiveShift({ setShiftParams, @@ -461,6 +476,7 @@ ORDER by ??, ?? subscriber.unsubscribe(); }; }, [ + locationSrv, productionDate, eventBus, setShiftParams, @@ -471,6 +487,7 @@ ORDER by ??, ?? props.timeRange.from, props.timeRange.to, shiftSelectorPluginPanel, + getRefreshRate, ]); useEffect(() => { diff --git a/src/module.ts b/src/module.ts index f579760..be722a3 100644 --- a/src/module.ts +++ b/src/module.ts @@ -111,11 +111,68 @@ export const plugin = new PanelPlugin(ShiftSelector).setPanelOptions((builder) = category: ['Behavior'], path: 'autoSelectShiftGroup', showIf: (c: any) => c.isAutoSelectShift, - name: 'Select group', + name: 'Group UUID', description: 'In case your panel contains multiple shift groups you can specify a certain group to cycle through in real-time mode. Scope to specific shift group (provide the group uuid). Once your group is set, the border outline of that group should be colored orange.', defaultValue: '', }) + .addSelect({ + category: ['Behavior'], + path: '_refreshInterval', + showIf: (c: any) => c.isAutoSelectShift, + name: 'Custom refresh interval', + description: + 'Determine a custom dashboard refresh interval.', + defaultValue: 60 * 1000, + settings: { + options: [ + { + label: '5 seconds', + value: 5 * 1000, + }, + { + label: '10 seconds', + value: 10 * 1000, + }, + { + label: '30 seconds', + value: 30 * 1000, + }, + { + label: '1 minute', + value: 60 * 1000, + }, + { + label: '30 minutes', + value: 30 * 60 * 1000, + }, + { + label: '1 hour', + value: 60 * 60 * 1000, + }, + { + label: '6 hour', + value: 6 * 60 * 60 * 1000, + }, + { + label: '12 hour', + value: 12 * 60 * 60 * 1000, + }, + { + label: '24 hour', + value: 24 * 60 * 60 * 1000, + } + ] + } + }) + .addBooleanSwitch({ + showIf: (c: any) => c.isAutoSelectShift, + category: ['Behavior'], + path: 'isProgressbarVisible', + name: 'Show refresh progress', + description: 'Show or hide the progress of the refresh rate.', + defaultValue: true, + }) .addBooleanSwitch({ showIf: (c: any) => !c.isAutoSelectShift, category: ['Range Labels'], diff --git a/src/types.ts b/src/types.ts index cf159c4..6000162 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,6 +20,8 @@ export type TRangeButtonViewType = 'icon-only' | 'text-and-icon' | 'text-only'; export type TOptionButtonViewType = 'text-and-icon' | 'text-only'; export type TPropOptions = { + refreshInterval: string; + _refreshInterval: string; isDataSourceShifts: boolean; isAutoSelectShift: boolean; autoSelectShiftGroup: string; @@ -31,13 +33,13 @@ export type TPropOptions = { rangeOptionLabelStartEnd: string; rangeOptionLabelStart: string; rangeOptionLabelEnd: string; - refreshInterval: string; var_query_map_dynamic: string; var_query_map_static: string; var_label_mapping: string; shiftSelectorPluginPanel: NodeListOf; isShowRangeButtons: boolean; isShowProductionDateSelector: boolean; + isProgressbarVisible: boolean; }; export type datePartOptions = 'both' | 'from' | 'to'; @@ -128,6 +130,7 @@ export type ExtendedShiftData = { export enum vars { queryShiftsOptions = 'var_shifts_options', + queryShiftsGroup = 'var_shift_group', varQueryMapper = 'var_query_map', varDataModel = 'var_shifts_dataModel', varShiftsValuesName = 'shifts_values', diff --git a/src/utils.ts b/src/utils.ts index 15940bc..9406842 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ import { dateTime, dateTimeAsMoment } from '@grafana/data'; -import { Option, ShiftData, ShiftI, TExtendedShift, TMappings, TUpdateActiveShiftProps } from './types'; +import { Option, ShiftData, ShiftI, TExtendedShift, TMappings, TUpdateActiveShiftProps, vars } from './types'; export const dateTimeFormat = `YYYY-MM-DD HH:mm:ss`; export const fakeEpoc = '2009-10-17'; @@ -192,16 +192,27 @@ export function getRelativeDates() { }; } +export const getInitGroupUUID = (options: TUpdateActiveShiftProps['shifts']['options'], values: TUpdateActiveShiftProps['shifts']['values']) => { + const shifts = getShifts(options, values, dateTimeAsMoment().unix() * 1000); + const [initGroup] = Object.keys(shifts) || []; + + return initGroup +} + export const updateActiveShift = (props: TUpdateActiveShiftProps) => { - const isRealtimeActive = !!(props.isAutoSelectShift && props.autoSelectShiftGroup); - const relativeToDate = isRealtimeActive ? dateTimeAsMoment().unix() * 1000 : props.productionDate; + const queryShiftsGroup = new URLSearchParams(window.location.search).get(vars.queryShiftsGroup); + const relativeToDate = !!props.isAutoSelectShift ? dateTimeAsMoment().unix() * 1000 : props.productionDate; const shifts = getShifts(props.shifts.options, props.shifts.values, relativeToDate); - const [initGroup] = Object.keys(shifts) || []; - let activeShifts = - props.autoSelectShiftGroup && shifts[props.autoSelectShiftGroup] - ? shifts[props.autoSelectShiftGroup] - : shifts[initGroup]; + const initGroup = getInitGroupUUID(props.shifts.options, props.shifts.values) + + let activeShifts = shifts[initGroup] + + if (queryShiftsGroup && shifts[queryShiftsGroup]) { + activeShifts = shifts[queryShiftsGroup] + } else if (props.autoSelectShiftGroup && shifts[props.autoSelectShiftGroup]) { + activeShifts = shifts[props.autoSelectShiftGroup] + } const activeShift = ((activeShifts as unknown) as TExtendedShift[]).find(({ _ }) => _.isActive);