Skip to content
Open
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
11 changes: 6 additions & 5 deletions internal/hud/client/log_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,17 @@ func (f LogFilter) matchesSinceFilter(line logstore.LogLine) bool {
// The implementation is identical to matchesFilter in web/src/OverviewLogPane.tsx.
// except for term filtering as tools like grep can be used from the CLI.
func (f LogFilter) Matches(line logstore.LogLine) bool {
// Check resource filter first - build events should also respect resource filtering
if !f.resources.Matches(line.ManifestName) {
return false
}

if line.BuildEvent != "" {
// Always leave in build event logs.
// Include build event logs that match the resource filter.
// This makes it easier to see which logs belong to which builds.
return true
}

if !f.resources.Matches(line.ManifestName) {
return false
}

isBuild := isBuildSpanID(line.SpanID)
if f.source == FilterSourceRuntime && isBuild {
return false
Expand Down
1 change: 1 addition & 0 deletions internal/hud/webview/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ func populateResourceInfoView(mt *store.ManifestTarget, r *v1alpha1.UIResource)
AllContainersReady: store.AllPodContainersReady(pod),
PodRestarts: kState.VisiblePodContainerRestarts(podID),
DisplayNames: kState.EntityDisplayNames(),
Namespace: kState.GetNamespace(),
}
if podID != "" {
rK8s.SpanID = string(k8sconv.SpanIDForPod(mt.Manifest.Name, podID))
Expand Down
14 changes: 14 additions & 0 deletions internal/store/runtime_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,20 @@ func (s K8sRuntimeState) EntityDisplayNames() []string {
return k8s.UniqueNamesMeta(entities, 2)
}

// GetNamespace returns the Kubernetes namespace for the deployed resources.
// It extracts the namespace from the first deployed reference if available.
func (s K8sRuntimeState) GetNamespace() string {
if s.ApplyFilter == nil || len(s.ApplyFilter.DeployedRefs) == 0 {
// Fall back to pod namespace if no deployed refs
pod := s.MostRecentPod()
if pod.Name != "" {
return pod.Namespace
}
return ""
}
return s.ApplyFilter.DeployedRefs[0].Namespace
}

type objectRefMeta struct {
v1.ObjectReference
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/core/v1alpha1/uiresource_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,11 @@ type UIResourceKubernetes struct {
// for this resource.
// +optional
DisplayNames []string `json:"displayNames,omitempty" protobuf:"bytes,9,rep,name=displayNames"`

// The Kubernetes namespace where resources are deployed.
// This allows grouping resources by namespace in the UI.
// +optional
Namespace string `json:"namespace,omitempty" protobuf:"bytes,10,opt,name=namespace"`
}

// UIResourceCompose contains status information specific to Docker Compose.
Expand Down
108 changes: 108 additions & 0 deletions web/src/OverviewTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ import Features, { Flag, useFeatures } from "./feature"
import { Hold } from "./Hold"
import {
getResourceLabels,
getResourceNamespace,
groupResourcesByNamespace,
GroupByLabelView,
GroupByNamespaceView,
orderLabels,
resourcesHaveNamespaces,
TILTFILE_LABEL,
UNLABELED_LABEL,
} from "./labels"
Expand Down Expand Up @@ -828,6 +832,97 @@ export function TableGroupedByLabels({
)
}

export function TableGroupedByNamespace({
resources,
buttons,
}: TableWrapperProps) {
const features = useFeatures()
const logAlertIndex = useLogAlertIndex()

// Group resources by namespace
const data = useMemo(() => {
const namespacesToResources: { [namespace: string]: RowValues[] } = {}
let ungrouped: RowValues[] = []

resources?.forEach((r) => {
const namespace = getResourceNamespace(r)
const isTiltfile = r.metadata?.name === ResourceName.tiltfile
const tableCell = uiResourceToCell(r, buttons, logAlertIndex)

if (namespace) {
if (!namespacesToResources.hasOwnProperty(namespace)) {
namespacesToResources[namespace] = []
}
namespacesToResources[namespace].push(tableCell)
} else if (isTiltfile) {
ungrouped.push(tableCell)
} else {
ungrouped.push(tableCell)
}
}) || []

const namespaces = Object.keys(namespacesToResources).sort()
return { namespaces, namespacesToResources, ungrouped }
}, [resources, buttons, logAlertIndex])

const totalOrder = useMemo(() => {
let totalOrder: RowValues[] = []
data.namespaces.forEach((namespace) =>
totalOrder.push(...enabledRowsFirst(data.namespacesToResources[namespace]))
)
totalOrder.push(...enabledRowsFirst(data.ungrouped))
return totalOrder
}, [data])

let [focused, setFocused] = useState("")

// Global table settings are currently used to sort multiple
// tables by the same column
const [globalTableSettings, setGlobalTableSettings] =
useState<UseSortByState<RowValues>>()

const useControlledState = (state: TableState<RowValues>) =>
useMemo(() => {
return { ...state, ...globalTableSettings }
}, [state, globalTableSettings])

const setGlobalSortBy = (columnId: string) => {
const sortBy = calculateNextSort(columnId, globalTableSettings?.sortBy)
setGlobalTableSettings({ sortBy })
}

return (
<>
{data.namespaces.map((namespace) => (
<TableGroup
key={`namespace-${namespace}`}
label={`${namespace} (namespace)`}
data={data.namespacesToResources[namespace]}
columns={COLUMNS}
useControlledState={useControlledState}
setGlobalSortBy={setGlobalSortBy}
focused={focused}
/>
))}
{data.ungrouped.length > 0 && (
<TableGroup
label="No namespace"
data={data.ungrouped}
columns={COLUMNS}
useControlledState={useControlledState}
setGlobalSortBy={setGlobalSortBy}
focused={focused}
/>
)}
<OverviewTableKeyboardShortcuts
focused={focused}
setFocused={setFocused}
rows={totalOrder}
/>
</>
)
}

export function TableWithoutGroups({ resources, buttons }: TableWrapperProps) {
const features = useFeatures()
const logAlertIndex = useLogAlertIndex()
Expand Down Expand Up @@ -862,6 +957,9 @@ function OverviewTableContent(props: OverviewTableProps) {
const resourcesHaveLabels =
props.view.uiResources?.some((r) => getResourceLabels(r).length > 0) ||
false
const resourcesHaveNamespaces =
props.view.uiResources?.some((r) => getResourceNamespace(r) !== undefined) ||
false

const { options } = useResourceListOptions()
const resourceFilterApplied = options.resourceNameFilter.length > 0
Expand All @@ -877,6 +975,9 @@ function OverviewTableContent(props: OverviewTableProps) {
// and no resource name filter is applied
const displayResourceGroups =
labelsEnabled && resourcesHaveLabels && !resourceFilterApplied
// Namespace groups are displayed when no label groups and resources have namespaces
const displayNamespaceGroups =
!resourceFilterApplied && !displayResourceGroups && resourcesHaveNamespaces

if (displayResourceGroups) {
return (
Expand All @@ -885,6 +986,13 @@ function OverviewTableContent(props: OverviewTableProps) {
buttons={props.view.uiButtons}
/>
)
} else if (displayNamespaceGroups) {
return (
<TableGroupedByNamespace
resources={resourcesToDisplay}
buttons={props.view.uiButtons}
/>
)
} else {
// The label group tip is only displayed if labels are enabled but not used
const displayLabelGroupsTip = labelsEnabled && !resourcesHaveLabels
Expand Down
2 changes: 2 additions & 0 deletions web/src/SidebarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class SidebarItem {
hold: Hold | null = null
targetType: string
stopBuildButton?: UIButton
resource: UIResource

/**
* Create a pared down SidebarItem from a ResourceView
Expand Down Expand Up @@ -70,6 +71,7 @@ class SidebarItem {
this.hold = status.waiting ? new Hold(status.waiting) : null
this.targetType = resourceTargetType(res)
this.stopBuildButton = stopBuildButton
this.resource = res
}
}

Expand Down
91 changes: 89 additions & 2 deletions web/src/SidebarResources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ import {
import { FeaturesContext, Flag, useFeatures } from "./feature"
import {
GroupByLabelView,
GroupByNamespaceView,
orderLabels,
TILTFILE_LABEL,
UNLABELED_LABEL,
getResourceNamespace,
groupResourcesByNamespace,
resourcesHaveNamespaces,
} from "./labels"
import { OverviewSidebarOptions } from "./OverviewSidebarOptions"
import PathBuilder from "./PathBuilder"
Expand All @@ -40,7 +44,7 @@ import SidebarItemView, {
import SidebarKeyboardShortcuts from "./SidebarKeyboardShortcuts"
import { AnimDuration, Color, Font, FontSize, SizeUnit } from "./style-helpers"
import { startBuild } from "./trigger"
import { ResourceName, ResourceStatus, ResourceView } from "./types"
import { ResourceName, ResourceStatus, ResourceView, UIResource } from "./types"
import { useStarredResources } from "./StarredResourcesContext"

export type SidebarProps = {
Expand Down Expand Up @@ -387,6 +391,38 @@ function resourcesLabelView(
return { labels, labelsToResources, tiltfile, unlabeled }
}

function resourcesNamespaceView(
items: SidebarItem[]
): GroupByNamespaceView<SidebarItem> {
const namespacesToResources: { [key: string]: SidebarItem[] } = {}
const ungrouped: SidebarItem[] = []
const namespacesSet = new Set<string>()

items.forEach((item) => {
const resource = item.resource
const namespace = getResourceNamespace(resource)

if (namespace) {
if (!namespacesToResources[namespace]) {
namespacesToResources[namespace] = []
}
namespacesToResources[namespace].push(item)
namespacesSet.add(namespace)
} else if (!item.isTiltfile) {
ungrouped.push(item)
}
})

// Sort namespaces alphabetically, with "default" first if present
const namespaces = Array.from(namespacesSet).sort((a, b) => {
if (a === "default") return -1
if (b === "default") return 1
return a.localeCompare(b)
})

return { namespaces, namespacesToResources, ungrouped }
}

function SidebarGroupedByLabels(props: SidebarGroupedByProps) {
const { labels, labelsToResources, tiltfile, unlabeled } = resourcesLabelView(
props.items
Expand Down Expand Up @@ -434,6 +470,45 @@ function SidebarGroupedByLabels(props: SidebarGroupedByProps) {
)
}

function SidebarGroupedByNamespace(props: SidebarGroupedByProps) {
const { namespaces, namespacesToResources, ungrouped } = resourcesNamespaceView(
props.items
)

// Build total order similar to label grouping
let totalOrder: SidebarItem[] = []
namespaces.map((namespace) => {
totalOrder.push(...enabledItemsFirst(namespacesToResources[namespace]))
})
totalOrder.push(...enabledItemsFirst(ungrouped))

return (
<>
{namespaces.map((namespace) => (
<SidebarGroupListSection
{...props}
key={`sidebarItem-namespace-${namespace}`}
label={`${namespace} (namespace)`}
items={namespacesToResources[namespace]}
/>
))}
{ungrouped.length > 0 && (
<SidebarGroupListSection
{...props}
label="No namespace"
items={ungrouped}
/>
)}
<SidebarKeyboardShortcuts
selected={props.selected}
items={totalOrder}
onStartBuild={props.onStartBuild}
resourceView={props.resourceView}
/>
</>
)
}

function hasAlerts(item: SidebarItem): boolean {
return item.buildAlertCount > 0 || item.runtimeAlertCount > 0
}
Expand Down Expand Up @@ -511,12 +586,18 @@ export class SidebarResources extends React.Component<SidebarProps> {
const resourcesHaveLabels = this.props.items.some(
(item) => item.labels.length > 0
)
const resourcesHaveNamespaces = this.props.items.some(
(item) => getResourceNamespace(item.resource) !== undefined
)

// The label group tip is only displayed if labels are enabled but not used
const displayLabelGroupsTip = labelsEnabled && !resourcesHaveLabels
// The label group view does not display if a resource name filter is applied
const displayLabelGroups =
!resourceFilterApplied && labelsEnabled && resourcesHaveLabels
// The namespace group view displays if no label groups and resources have namespaces
const displayNamespaceGroups =
!resourceFilterApplied && !displayLabelGroups && resourcesHaveNamespaces

return (
<SidebarResourcesRoot
Expand Down Expand Up @@ -546,6 +627,12 @@ export class SidebarResources extends React.Component<SidebarProps> {
items={filteredItems}
onStartBuild={this.startBuildOnSelected}
/>
) : displayNamespaceGroups ? (
<SidebarGroupedByNamespace
{...this.props}
items={filteredItems}
onStartBuild={this.startBuildOnSelected}
/>
) : (
<SidebarListSection
{...this.props}
Expand All @@ -555,7 +642,7 @@ export class SidebarResources extends React.Component<SidebarProps> {
)}
</SidebarResourcesContent>
{/* The label groups display handles the keyboard shortcuts separately. */}
{displayLabelGroups ? null : (
{displayLabelGroups || displayNamespaceGroups ? null : (
<SidebarKeyboardShortcuts
selected={this.props.selected}
items={enabledItemsFirst(filteredItems)}
Expand Down
6 changes: 6 additions & 0 deletions web/src/core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4070,6 +4070,12 @@ export interface UIResourceKubernetes {
* +optional
*/
displayNames?: string[]
/**
* The Kubernetes namespace where resources are deployed.
* This allows grouping resources by namespace in the UI.
* +optional
*/
namespace?: string
}
/**
* UIResourceCompose contains status information specific to Docker Compose.
Expand Down
Loading