mirror of https://github.com/grafana/grafana
Explore: Polish UI of logs navigation (#33910)
* Update UI * Update UI, return spinner * Add title to Scroll to top button * Update public/app/features/explore/Logs.tsx Co-authored-by: Gábor Farkas <gabor.farkas@gmail.com> * Update public/app/features/explore/LogsNavigation.test.tsx Co-authored-by: Gábor Farkas <gabor.farkas@gmail.com> * Remove unnecessary memoization * Update public/app/features/explore/LogsNavigationPages.tsx Co-authored-by: Gábor Farkas <gabor.farkas@gmail.com>pull/33839/head
parent
2ded2aef71
commit
9ace76a718
@ -0,0 +1,45 @@ |
||||
import React, { ComponentProps } from 'react'; |
||||
import { render, screen } from '@testing-library/react'; |
||||
import { LogsNavigationPages } from './LogsNavigationPages'; |
||||
|
||||
type LogsNavigationPagesProps = ComponentProps<typeof LogsNavigationPages>; |
||||
|
||||
const setup = (propOverrides?: object) => { |
||||
const props: LogsNavigationPagesProps = { |
||||
pages: [ |
||||
{ |
||||
logsRange: { from: 1619081941000, to: 1619081945930 }, |
||||
queryRange: { from: 1619081645930, to: 1619081945930 }, |
||||
}, |
||||
{ |
||||
logsRange: { from: 1619081951000, to: 1619081955930 }, |
||||
queryRange: { from: 1619081655930, to: 1619081955930 }, |
||||
}, |
||||
], |
||||
currentPageIndex: 0, |
||||
oldestLogsFirst: false, |
||||
timeZone: 'local', |
||||
loading: false, |
||||
changeTime: jest.fn(), |
||||
...propOverrides, |
||||
}; |
||||
|
||||
return render(<LogsNavigationPages {...props} />); |
||||
}; |
||||
|
||||
describe('LogsNavigationPages', () => { |
||||
it('should render logs navigation pages', () => { |
||||
setup(); |
||||
expect(screen.getByTestId('logsNavigationPages')).toBeInTheDocument(); |
||||
}); |
||||
it('should render logs pages with correct range if normal order', () => { |
||||
setup(); |
||||
expect(screen.getByText(/02:59:05 — 02:59:01/i)).toBeInTheDocument(); |
||||
expect(screen.getByText(/02:59:15 — 02:59:11/i)).toBeInTheDocument(); |
||||
}); |
||||
it('should render logs pages with correct range if flipped order', () => { |
||||
setup({ oldestLogsFirst: true }); |
||||
expect(screen.getByText(/02:59:11 — 02:59:15/i)).toBeInTheDocument(); |
||||
expect(screen.getByText(/02:59:01 — 02:59:05/i)).toBeInTheDocument(); |
||||
}); |
||||
}); |
@ -0,0 +1,121 @@ |
||||
import React from 'react'; |
||||
import { css, cx } from 'emotion'; |
||||
import { dateTimeFormat, systemDateFormats, TimeZone, GrafanaTheme, AbsoluteTimeRange } from '@grafana/data'; |
||||
import { useTheme, stylesFactory, CustomScrollbar, Spinner } from '@grafana/ui'; |
||||
import { LogsPage } from './LogsNavigation'; |
||||
|
||||
type Props = { |
||||
pages: LogsPage[]; |
||||
currentPageIndex: number; |
||||
oldestLogsFirst: boolean; |
||||
timeZone: TimeZone; |
||||
loading: boolean; |
||||
changeTime: (range: AbsoluteTimeRange) => void; |
||||
}; |
||||
|
||||
export function LogsNavigationPages({ |
||||
pages, |
||||
currentPageIndex, |
||||
oldestLogsFirst, |
||||
timeZone, |
||||
loading, |
||||
changeTime, |
||||
}: Props) { |
||||
const formatTime = (time: number) => { |
||||
return `${dateTimeFormat(time, { |
||||
format: systemDateFormats.interval.second, |
||||
timeZone: timeZone, |
||||
})}`;
|
||||
}; |
||||
|
||||
const createPageContent = (page: LogsPage, index: number) => { |
||||
if (currentPageIndex === index && loading) { |
||||
return <Spinner />; |
||||
} |
||||
const topContent = formatTime(oldestLogsFirst ? page.logsRange.from : page.logsRange.to); |
||||
const bottomContent = formatTime(oldestLogsFirst ? page.logsRange.to : page.logsRange.from); |
||||
return `${topContent} — ${bottomContent}`; |
||||
}; |
||||
|
||||
const theme = useTheme(); |
||||
const styles = getStyles(theme, loading); |
||||
|
||||
return ( |
||||
<CustomScrollbar autoHide> |
||||
<div className={styles.pagesWrapper} data-testid="logsNavigationPages"> |
||||
<div className={styles.pagesContainer}> |
||||
{pages.map((page: LogsPage, index: number) => ( |
||||
<div |
||||
data-testid={`page${index + 1}`} |
||||
className={styles.page} |
||||
key={page.queryRange.to} |
||||
onClick={() => !loading && changeTime({ from: page.queryRange.from, to: page.queryRange.to })} |
||||
> |
||||
<div className={cx(styles.line, { selectedBg: currentPageIndex === index })} /> |
||||
<div className={cx(styles.time, { selectedText: currentPageIndex === index })}> |
||||
{createPageContent(page, index)} |
||||
</div> |
||||
</div> |
||||
))} |
||||
</div> |
||||
</div> |
||||
</CustomScrollbar> |
||||
); |
||||
} |
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme, loading: boolean) => { |
||||
return { |
||||
pagesWrapper: css` |
||||
height: 100%; |
||||
padding-left: ${theme.spacing.xs}; |
||||
display: flex; |
||||
flex-direction: column; |
||||
overflow-y: scroll; |
||||
&::after { |
||||
content: ''; |
||||
display: block; |
||||
background: repeating-linear-gradient( |
||||
135deg, |
||||
${theme.colors.bg1}, |
||||
${theme.colors.bg1} 5px, |
||||
${theme.colors.bg2} 5px, |
||||
${theme.colors.bg2} 15px |
||||
); |
||||
width: 3px; |
||||
height: inherit; |
||||
margin-bottom: 8px; |
||||
} |
||||
`,
|
||||
pagesContainer: css` |
||||
display: flex; |
||||
padding: 0; |
||||
flex-direction: column; |
||||
`,
|
||||
page: css` |
||||
display: flex; |
||||
margin: ${theme.spacing.md} 0; |
||||
cursor: ${loading ? 'auto' : 'pointer'}; |
||||
white-space: normal; |
||||
.selectedBg { |
||||
background: ${theme.colors.bgBlue2}; |
||||
} |
||||
.selectedText { |
||||
color: ${theme.colors.bgBlue2}; |
||||
} |
||||
`,
|
||||
line: css` |
||||
width: 3px; |
||||
height: 100%; |
||||
align-items: center; |
||||
background: ${theme.colors.textWeak}; |
||||
`,
|
||||
time: css` |
||||
width: 60px; |
||||
min-height: 80px; |
||||
font-size: ${theme.typography.size.sm}; |
||||
padding-left: ${theme.spacing.xs}; |
||||
display: flex; |
||||
align-items: center; |
||||
`,
|
||||
}; |
||||
}); |
Loading…
Reference in new issue