Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Console Pod Logs broken if not in a plural application #348

Merged
merged 2 commits into from
Apr 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions assets/src/components/apps/app/logs/LogContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export default function LogContent({
listRef,
setListRef,
logs,
name,
namespace,
loading,
fetchMore,
onScroll,
Expand Down Expand Up @@ -102,7 +102,7 @@ export default function LogContent({
listRef={listRef}
setListRef={setListRef}
setLoader={setLoader}
refreshKey={`${name}:${search}`}
refreshKey={`${namespace}:${search}`}
items={lines}
mapper={({ line, level, stream }, o) => (
<LogLine
Expand Down
49 changes: 28 additions & 21 deletions assets/src/components/apps/app/logs/Logs.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Input, SearchIcon, useSetBreadcrumbs } from '@pluralsh/design-system'
import { useCallback, useContext, useMemo, useState } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { useParams } from 'react-router-dom'
import { InstallationContext } from 'components/Installations'
import { toMap, useQueryParams } from 'components/utils/query'
import { Flex } from 'honorable'

Expand All @@ -13,10 +12,29 @@ import LogsFilters from './LogsFilters'
import { LogsCard } from './LogsCard'
import LogsFullScreen from './LogsFullScreen'

export default function Logs() {
export default function AppLogs() {
const { appName } = useParams()

const breadcrumbs = useMemo(
() => [
{ label: 'apps', url: '/' },
{ label: appName ?? '', url: `/apps/${appName}` },
{ label: 'logs', url: `/apps/${appName}/logs` },
],
[appName]
)

useSetBreadcrumbs(breadcrumbs)

if (!appName) {
return null
}

return <LogsBase namespace={appName} />
}

export function LogsBase({ namespace }: { namespace: string }) {
const query = useQueryParams()
const { applications } = useContext<any>(InstallationContext)
const [search, setSearch] = useState('')
const [labels, setLabels] = useState(toMap(query))

Expand All @@ -33,32 +51,20 @@ export default function Logs() {
[labels, setLabels]
)

const currentApp = applications.find((app) => app.name === appName)
const searchQuery = search.length > 0 ? ` |~ "${search}"` : ''
const labelList = Object.entries(labels).map(([name, value]) => ({
name,
value,
}))
const labelQuery = useMemo(
() =>
[...labelList, { name: 'namespace', value: appName }]
[...labelList, { name: 'namespace', value: namespace }]
.map(({ name, value }) => `${name}="${value}"`)
.join(','),
[labelList, appName]
[labelList, namespace]
)
const logQuery = `{${labelQuery}}${searchQuery}`

const breadcrumbs = useMemo(
() => [
{ label: 'apps', url: '/' },
{ label: appName ?? '', url: `/apps/${appName}` },
{ label: 'logs', url: `/apps/${appName}/logs` },
],
[appName]
)

useSetBreadcrumbs(breadcrumbs)

return (
<ScrollablePage
heading="Logs"
Expand All @@ -70,7 +76,7 @@ export default function Logs() {
grow={1}
>
<LogsFullScreen
application={currentApp}
namespace={namespace}
query={logQuery}
search={search}
setSearch={setSearch}
Expand All @@ -82,7 +88,7 @@ export default function Logs() {
/>
<LogsDownloader
query={logQuery}
repo={appName}
repo={namespace}
/>
<LogsFilters
search={search}
Expand All @@ -103,13 +109,14 @@ export default function Logs() {
startIcon={<SearchIcon size={14} />}
value={search}
onChange={({ target: { value } }) => setSearch(value)}
flexShrink={0}
/>
<LogsLabels
labels={labelList}
removeLabel={removeLabel}
/>
<LogsCard
application={currentApp}
namespace={namespace}
query={logQuery}
addLabel={addLabel}
/>
Expand Down
4 changes: 2 additions & 2 deletions assets/src/components/apps/app/logs/LogsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const POLL_INTERVAL = 10 * 1000
const LIMIT = 1000

export function LogsCard({
application: { name },
namespace,
query,
addLabel,
fullscreen = false,
Expand Down Expand Up @@ -51,7 +51,7 @@ export function LogsCard({
<LogContent
listRef={listRef}
setListRef={setListRef}
name={name}
namespace={namespace}
logs={data.logs}
setLoader={setLoader}
search={query}
Expand Down
4 changes: 2 additions & 2 deletions assets/src/components/apps/app/logs/LogsFullScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { LogsCard } from './LogsCard'
import LogsFilters from './LogsFilters'

export default function LogsFullScreen({
application,
namespace,
query,
search,
setSearch,
Expand Down Expand Up @@ -90,7 +90,7 @@ export default function LogsFullScreen({
removeLabel={removeLabel}
/>
<LogsCard
application={application}
namespace={namespace}
query={query}
addLabel={addLabel}
height="100%"
Expand Down
34 changes: 19 additions & 15 deletions assets/src/components/cluster/pods/Pod.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react'
import { Outlet, useParams } from 'react-router-dom'
import { TabPanel, useBreadcrumbs } from '@pluralsh/design-system'
import { useMemo, useRef } from 'react'
import { Outlet, useMatch, useParams } from 'react-router-dom'
import { TabPanel, useSetBreadcrumbs } from '@pluralsh/design-system'
import { useTheme } from 'styled-components'

import { ResponsiveLayoutSidecarContainer } from 'components/utils/layout/ResponsiveLayoutSidecarContainer'
Expand All @@ -17,19 +17,23 @@ export default function Node() {
const tabStateRef = useRef<any>()
const theme = useTheme()
const { name, namespace } = useParams()
const { setBreadcrumbs } = useBreadcrumbs()
const { tab } = useMatch('/pods/:namespace/:name/:tab')?.params || {}

// TODO: Investigate whether these links could more specific,
// based on where they navigated from, perhaps the `namespace` crumb
// could navigate to the Pods view already filtered for that namespace
useEffect(() => {
if (name && namespace) {
setBreadcrumbs([
{ label: 'pods', url: '/pods' }, // Add filter param here later maybe?
{ label: name, url: name },
])
}
}, [name, namespace, setBreadcrumbs])
const breadcrumbs = useMemo(
() => [
{ label: 'pods', url: '/pods' },
...(namespace ? [{ label: namespace, url: `/pods/${namespace}` }] : []),
...(namespace && name
? [{ label: name, url: `/pods/${namespace}/${name}` }]
: []),
...(tab && namespace && name
? [{ label: tab, url: `/pods/${namespace}/${name}/${tab}` }]
: []),
],
[name, namespace, tab]
)

useSetBreadcrumbs(breadcrumbs)

return (
<ResponsiveLayoutPage>
Expand Down
26 changes: 22 additions & 4 deletions assets/src/components/cluster/pods/PodInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { POD_INFO_Q } from '../queries'
import { SubTitle } from '../nodes/SubTitle'
import { ContainersList } from '../containers/ContainersList'

import { useNamespaceIsApp } from '../../hooks/useNamespaceIsApp'

import PodMetadata from './PodMetadata'
import PodConditions from './PodConditions'

Expand All @@ -27,14 +29,30 @@ export const statusesToRecord = (statuses?: Maybe<Maybe<ContainerStatus>[]>) =>
{} as Record<string, Maybe<ContainerStatus>>
)

function getLogUrl({ name, namespace }) {
return `/apps/${namespace}/logs?${asQuery({ pod: name })}`
function useGetLogUrl({
name,
namespace,
}: {
name?: string
namespace?: string
}) {
const isApp = useNamespaceIsApp(namespace)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we just pass this as an argument and ignore the potentially risky app name fetch?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some field on a Pod that I could use to determine if it's an app or not? Otherwise I'd just be pushing the app name fetch further up the render tree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not easily, I thought you might be able to determine it from context of where the component is called, eg if it's called from an /apps/ prefixed url use something like that link, otherwise non-app link?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, I need to know whether it's an app or not on the Pod page, where it's not obvious.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either that, or we always show the logs on the Pod page, even if there is an associated App page where you could also view the logs.


if (!namespace) {
return null
}

return isApp
? `/apps/${namespace}/logs${name ? `?${asQuery({ pod: name })}` : ''}`
: name
? `/pods/${namespace}/${name}/logs`
: null
}

function ViewLogsButton({ metadata }: any) {
if (!metadata) return null
const url = useGetLogUrl(metadata)

const url = getLogUrl(metadata)
if (!url) return null

return (
<Button
Expand Down
12 changes: 12 additions & 0 deletions assets/src/components/cluster/pods/PodLogs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { LogsBase } from 'components/apps/app/logs/Logs'
import { useParams } from 'react-router-dom'

export default function PodLogs() {
const { namespace } = useParams()

if (!namespace) {
return null
}

return <LogsBase namespace={namespace} />
}
32 changes: 23 additions & 9 deletions assets/src/components/cluster/pods/PodSideNav.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
import { Tab, TabList } from '@pluralsh/design-system'
import { useMatch } from 'react-router-dom'

import { useMatch, useParams } from 'react-router-dom'
import { LinkTabWrap } from 'components/utils/Tabs'
import { useMemo } from 'react'

import { useNamespaceIsApp } from '../../hooks/useNamespaceIsApp'

const useGetDirectory = (namespace = '') => {
const namespaceIsApp = useNamespaceIsApp(namespace)

const DIRECTORY = [
{ path: '', label: 'Info' },
{ path: 'events', label: 'Events' },
{ path: 'raw', label: 'Raw' },
]
return useMemo(() => {
const showLogs = !namespaceIsApp

return [
{ path: '', label: 'Info' },
{ path: 'events', label: 'Events' },
{ path: 'raw', label: 'Raw' },
...(showLogs ? [{ path: 'logs', label: 'Logs' }] : []),
]
}, [namespaceIsApp])
}

function NodesTabList({ tabStateRef }: any) {
const { namespace } = useParams()

const subpath =
useMatch('/pods/:namespace/:name/:subpath')?.params?.subpath || ''
const directory = useGetDirectory(namespace)

const currentTab = DIRECTORY.find(({ path }) => path === subpath)
const currentTab = directory.find(({ path }) => path === subpath)

return (
<TabList
Expand All @@ -23,7 +37,7 @@ function NodesTabList({ tabStateRef }: any) {
selectedKey: currentTab?.path,
}}
>
{DIRECTORY.map(({ label, path }) => (
{directory.map(({ label, path }) => (
<LinkTabWrap
key={path}
textValue={label}
Expand Down
1 change: 1 addition & 0 deletions assets/src/components/cluster/pods/PodsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ export const ColImages = columnHelper.accessor((row) => row?.images || [], {

return images.map((image) => (
<Tooltip
key={image}
label={image}
placement="left-start"
>
Expand Down
13 changes: 13 additions & 0 deletions assets/src/components/hooks/useNamespaceIsApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useContext, useMemo } from 'react'
import { InstallationContext } from 'components/Installations'

export const useNamespaceIsApp = (namespace = '') => {
const { applications } = useContext<any>(InstallationContext) as {
applications?: { name?: string }[]
}

return useMemo(
() => !!(namespace && applications?.some((app) => app?.name === namespace)),
[applications, namespace]
)
}
1 change: 1 addition & 0 deletions assets/src/components/layout/AppNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export function StatusPanel({ statuses, open, onClose }) {
</StatusPanelTopContainer>
{apps.map((app, i) => (
<AppStatusWrap
key={app?.name}
onClick={() => {
onClose()
navigate(`/apps/${app.name}`)
Expand Down
5 changes: 5 additions & 0 deletions assets/src/routes/clusterRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Pod from 'components/cluster/pods/Pod'
import PodInfo from 'components/cluster/pods/PodInfo'
import PodEvents from 'components/cluster/pods/PodEvents'
import PodRaw from 'components/cluster/pods/PodRaw'
import PodLogs from 'components/cluster/pods/PodLogs'
import Node from 'components/cluster/nodes/Node'
import Nodes from 'components/cluster/nodes/Nodes'
import NodeEvents from 'components/cluster/nodes/NodeEvents'
Expand Down Expand Up @@ -40,6 +41,10 @@ export const clusterRoutes = [
path="raw"
element={<PodRaw />}
/>
<Route
path="logs"
element={<PodLogs />}
/>
</Route>,

/* Nodes */
Expand Down