Skip to content

Commit 5ca7c73

Browse files
committed
fix: use react-keybind for logs resize button & expand all logs accordion
1 parent 984f6c5 commit 5ca7c73

File tree

7 files changed

+95
-67
lines changed

7 files changed

+95
-67
lines changed

package-lock.json

Lines changed: 9 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "0.3.7",
3+
"version": "0.3.6-beta-5",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",
@@ -87,6 +87,7 @@
8787
"react-mde": "^11.5.0",
8888
"react-router": "^5.3.0",
8989
"react-router-dom": "^5.3.0",
90+
"react-keybind": "^0.9.4",
9091
"rxjs": "^7.8.1",
9192
"yaml": "^2.4.1"
9293
},

src/Common/Tooltip/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const KEYBOARD_KEYS_MAP = {
66
Control: isMacOS ? '⌘' : 'Ctrl',
77
Shift: '⇧',
88
F: 'F',
9+
E: 'E',
910
} as const
1011

1112
export type SupportedKeyboardKeysType = keyof typeof KEYBOARD_KEYS_MAP

src/Shared/Components/CICDHistory/History.components.tsx

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
*/
1616

1717
import Tippy from '@tippyjs/react'
18-
import { useEffect } from 'react'
18+
import { useEffect, useRef } from 'react'
1919
import { useLocation } from 'react-router-dom'
20-
import { ClipboardButton, GenericEmptyState, extractImage, useKeyDown, useSuperAdmin } from '../../../Common'
20+
import { withShortcut, IWithShortcut } from 'react-keybind'
21+
import { ClipboardButton, GenericEmptyState, Tooltip, extractImage, useSuperAdmin } from '../../../Common'
2122
import { EMPTY_STATE_STATUS } from '../../constants'
2223
import { ReactComponent as DropDownIcon } from '../../../Assets/Icon/ic-chevron-down.svg'
2324
import { GitChangesType, LogResizeButtonType, ScrollerType } from './types'
@@ -27,53 +28,57 @@ import { ReactComponent as ZoomIn } from '../../../Assets/Icon/ic-fullscreen.svg
2728
import { ReactComponent as ZoomOut } from '../../../Assets/Icon/ic-exit-fullscreen.svg'
2829
import './cicdHistory.scss'
2930

30-
export const LogResizeButton = ({ fullScreenView, setFullScreenView }: LogResizeButtonType): JSX.Element => {
31-
const { pathname } = useLocation()
31+
export const LogResizeButton = withShortcut(
32+
({ fullScreenView, setFullScreenView, shortcut }: LogResizeButtonType & IWithShortcut): JSX.Element => {
33+
const { pathname } = useLocation()
34+
const zoomButtonRef = useRef<HTMLDivElement>(null)
3235

33-
const keys = useKeyDown()
36+
const toggleFullScreen = (): void => {
37+
// NOTE: need to use ref due to the problem of stale function reference after registering the callback
38+
setFullScreenView(!(zoomButtonRef.current.dataset.isFullscreenView === 'true'))
39+
}
3440

35-
const toggleFullScreen = (): void => {
36-
setFullScreenView(!fullScreenView)
37-
}
41+
useEffect(() => {
42+
shortcut.registerShortcut(toggleFullScreen, ['f'], 'ToggleFullscreen', 'Enter/Exit fullscreen')
43+
shortcut.registerShortcut(
44+
() => setFullScreenView(false),
45+
['Escape'],
46+
'ToggleFullscreen',
47+
'Enter/Exit fullscreen',
48+
)
3849

39-
useEffect(() => {
40-
if (!pathname.includes('/logs')) {
41-
return
42-
}
43-
// eslint-disable-next-line default-case
44-
switch (keys.join('')) {
45-
case 'f':
46-
toggleFullScreen()
47-
break
48-
case 'Escape':
49-
setFullScreenView(false)
50-
break
51-
}
52-
}, [keys])
50+
return () => {
51+
shortcut.unregisterShortcut(['f'])
52+
shortcut.unregisterShortcut(['Escape'])
53+
}
54+
}, [pathname.includes('/logs')])
5355

54-
return (
55-
pathname.includes('/logs') && (
56-
<Tippy
57-
placement="top"
58-
arrow={false}
59-
className="default-tt"
60-
content={fullScreenView ? 'Exit fullscreen (f)' : 'Enter fullscreen (f)'}
61-
>
62-
<div>
63-
{fullScreenView ? (
64-
<ZoomOut className="zoom zoom--out pointer dc__zi-4" onClick={toggleFullScreen} />
65-
) : (
66-
<ZoomIn className="zoom zoom--in pointer dc__zi-4" onClick={toggleFullScreen} />
67-
)}
68-
</div>
69-
</Tippy>
56+
return (
57+
pathname.includes('/logs') && (
58+
<Tooltip
59+
placement="left"
60+
shortcutKeyCombo={{
61+
text: fullScreenView ? 'Exit fullscreen' : 'Enter fullscreen',
62+
combo: ['F'] as const,
63+
}}
64+
>
65+
<div
66+
ref={zoomButtonRef}
67+
data-is-fullscreen-view={fullScreenView}
68+
className={`zoom ${fullScreenView ? 'zoom--out' : 'zoom--in'} pointer dc__zi-4 flex`}
69+
onClick={toggleFullScreen}
70+
>
71+
{fullScreenView ? <ZoomOut className="icon-dim-16" /> : <ZoomIn className="icon-dim-16" />}
72+
</div>
73+
</Tooltip>
74+
)
7075
)
71-
)
72-
}
76+
},
77+
)
7378

7479
export const Scroller = ({ scrollToTop, scrollToBottom, style }: ScrollerType): JSX.Element => (
7580
<div style={style} className="dc__element-scroller flex column top br-4">
76-
<Tippy className="default-tt" arrow={false} content="Scroll to Top">
81+
<Tippy className="default-tt" arrow={false} content="Scroll to Top" placement="left">
7782
<button
7883
className="flex"
7984
disabled={!scrollToTop}
@@ -84,7 +89,7 @@ export const Scroller = ({ scrollToTop, scrollToBottom, style }: ScrollerType):
8489
<DropDownIcon className="rotate" style={{ ['--rotateBy' as any]: '180deg' }} />
8590
</button>
8691
</Tippy>
87-
<Tippy className="default-tt" arrow={false} content="Scroll to Bottom">
92+
<Tippy className="default-tt" arrow={false} content="Scroll to Bottom" placement="left">
8893
<button
8994
className="flex"
9095
disabled={!scrollToBottom}

src/Shared/Components/CICDHistory/LogsRenderer.tsx

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { ANSI_UP_REGEX, ComponentSizeType } from '@Shared/constants'
2222
import { escapeRegExp } from '@Shared/Helpers'
2323
import { ReactComponent as ICExpandAll } from '@Icons/ic-expand-all.svg'
2424
import { ReactComponent as ICCollapseAll } from '@Icons/ic-collapse-all.svg'
25+
import { withShortcut, IWithShortcut } from 'react-keybind'
2526
import {
2627
Progressing,
2728
Host,
@@ -30,7 +31,6 @@ import {
3031
ROUTES,
3132
SearchBar,
3233
useUrlFilters,
33-
stopPropagation,
3434
Tooltip,
3535
} from '../../../Common'
3636
import LogStageAccordion from './LogStageAccordion'
@@ -170,12 +170,13 @@ const useCIEventSource = (url: string, maxLength?: number): [string[], EventSour
170170
return [dataVal, eventSourceRef.current, logsNotAvailableError]
171171
}
172172

173-
export const LogsRenderer = ({
173+
const LogsRenderer = ({
174174
triggerDetails,
175175
isBlobStorageConfigured,
176176
parentType,
177177
fullScreenView,
178-
}: LogsRendererType): JSX.Element => {
178+
shortcut,
179+
}: LogsRendererType & IWithShortcut) => {
179180
const { pipelineId, envId, appId } = useParams<DeploymentHistoryBaseParamsType>()
180181
const logsURL =
181182
parentType === HistoryComponentType.CI
@@ -188,6 +189,7 @@ export const LogsRenderer = ({
188189
// State for logs list in case no stages are available
189190
const [logsList, setLogsList] = useState<string[]>([])
190191
const { searchKey, handleSearch } = useUrlFilters()
192+
const accordionExpansionStateTogglerButtonRef = useRef<HTMLButtonElement>(null)
191193

192194
const areAllStagesExpanded = useMemo(() => stageList.every((item) => item.isOpen), [stageList])
193195

@@ -384,6 +386,29 @@ export const LogsRenderer = ({
384386
// And for other cases we would use handleSearchEnter
385387
}, [streamDataList, areEventsProgressing])
386388

389+
const handleToggleOpenAllStages = () => {
390+
setStageList((prev) =>
391+
prev.map((stage) => ({
392+
...stage,
393+
// NOTE: need to use ref due to the problem of stale function reference after registering the callback
394+
isOpen: !(accordionExpansionStateTogglerButtonRef.current.dataset.toggleState === 'true'),
395+
})),
396+
)
397+
}
398+
399+
useEffect(() => {
400+
shortcut.registerShortcut(
401+
handleToggleOpenAllStages,
402+
['e'],
403+
'ExpandCollapseLogStages',
404+
'Expand/Collapse all log stages',
405+
)
406+
407+
return () => {
408+
shortcut.unregisterShortcut(['e'])
409+
}
410+
}, [])
411+
387412
const handleSearchEnter = (searchText: string) => {
388413
handleSearch(searchText)
389414
const newStageList = getStageListFromStreamData(searchText)
@@ -402,15 +427,6 @@ export const LogsRenderer = ({
402427
setStageList(newLogs)
403428
}
404429

405-
const handleToggleOpenAllStages = () => {
406-
setStageList(
407-
stageList.map((stage) => ({
408-
...stage,
409-
isOpen: !areAllStagesExpanded,
410-
})),
411-
)
412-
}
413-
414430
const renderLogs = () => {
415431
if (areStagesAvailable) {
416432
return (
@@ -427,11 +443,7 @@ export const LogsRenderer = ({
427443
backgroundColor: '#0C1021',
428444
}}
429445
>
430-
<div
431-
className="flexbox logs-renderer__search-bar logs-renderer__filters-border-bottom pl-12"
432-
// Doing this since we have binded 'f' with full screen and SearchVar has not exposed event on search, so on pressing f it goes to full screen
433-
onKeyDown={stopPropagation}
434-
>
446+
<div className="flexbox logs-renderer__search-bar logs-renderer__filters-border-bottom pl-12">
435447
<SearchBar
436448
noBackgroundAndBorder
437449
containerClassName="w-100"
@@ -445,16 +457,18 @@ export const LogsRenderer = ({
445457
<Tooltip
446458
shortcutKeyCombo={{
447459
text: areAllStagesExpanded ? 'Collapse all stages' : 'Expand all stages',
448-
combo: ['Control', 'Shift', 'F'] as const,
460+
combo: ['E'] as const,
449461
}}
450462
className="dc__mxw-500"
451463
placement="left"
452464
>
453465
<button
454466
type="button"
455-
className="dc__unset-button-styles px-10 flex dc__bg-n0--opacity-0_2"
467+
className="dc__unset-button-styles px-10 flex dc__bg-n0--opacity-0_2 pointer"
456468
onClick={handleToggleOpenAllStages}
457469
aria-label="Expand all stages"
470+
data-toggle-state={areAllStagesExpanded}
471+
ref={accordionExpansionStateTogglerButtonRef}
458472
>
459473
{areAllStagesExpanded ? (
460474
<ICCollapseAll className="icon-dim-16 dc__no-shrink dc__transition--transform scn-0" />
@@ -522,4 +536,4 @@ export const LogsRenderer = ({
522536
: renderLogs()
523537
}
524538

525-
export default LogsRenderer
539+
export default withShortcut(LogsRenderer)

src/Shared/Components/CICDHistory/TriggerOutput.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ import { getTagDetails, getTriggerDetails, cancelCiTrigger, cancelPrePostCdTrigg
6464
import { DEFAULT_ENV, TIMEOUT_VALUE, WORKER_POD_BASE_URL } from './constants'
6565
import { GitTriggers } from '../../types'
6666
import warn from '../../../Assets/Icon/ic-warning.svg'
67-
import { LogsRenderer } from './LogsRenderer'
67+
import LogsRenderer from './LogsRenderer'
6868
import DeploymentDetailSteps from './DeploymentDetailSteps'
6969
import { DeploymentHistoryDetailedView, DeploymentHistoryConfigList } from './DeploymentHistoryDiff'
7070
import { GitChanges, Scroller } from './History.components'

src/Shared/Components/CICDHistory/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export * from './service'
2626
export * from './History.components'
2727
export * from './utils'
2828
export * from './TriggerOutput'
29-
export * from './LogsRenderer'
29+
export { default as LogsRenderer } from './LogsRenderer'
3030
export * from './DeploymentHistoryDiff'
3131
export * from './CiPipelineSourceConfig'
3232
export * from './StatusFilterButtonComponent'

0 commit comments

Comments
 (0)