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 all Age, LastSeen, and FirstSeen displays #4990

Merged
merged 3 commits into from
Mar 17, 2022
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@
"mobx": "^6.3.7",
"mobx-observable-history": "^2.0.3",
"mobx-react": "^7.2.1",
"mobx-utils": "^6.0.4",
"mock-fs": "^5.1.2",
"moment": "^2.29.1",
"moment-timezone": "^0.5.34",
Expand Down
6 changes: 6 additions & 0 deletions src/common/k8s-api/endpoints/events.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,18 @@ export class KubeEvent extends KubeObject {
return `${component} ${host || ""}`;
}

/**
* @deprecated This function is not reactive to changing of time. If rendering use `<ReactiveDuration />` instead
*/
getFirstSeenTime() {
const diff = moment().diff(this.firstTimestamp);

return formatDuration(diff, true);
}

/**
* @deprecated This function is not reactive to changing of time. If rendering use `<ReactiveDuration />` instead
*/
getLastSeenTime() {
const diff = moment().diff(this.lastTimestamp);

Expand Down
18 changes: 18 additions & 0 deletions src/common/k8s-api/kube-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,28 @@ export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata
return this.metadata.namespace || undefined;
}

/**
* This function computes the number of milliseconds from the UNIX EPOCH to the
* creation timestamp of this object.
*/
getCreationTimestamp() {
return new Date(this.metadata.creationTimestamp).getTime();
}

/**
* @deprecated This function computes a new "now" on every call which might cause subtle issues if called multiple times
*
* NOTE: Generally you can use `getCreationTimestamp` instead.
*/
getTimeDiffFromNow(): number {
return Date.now() - new Date(this.metadata.creationTimestamp).getTime();
}

/**
* @deprecated This function computes a new "now" on every call might cause subtle issues if called multiple times
*
* NOTE: this function also is not reactive to updates in the current time so it should not be used for renderering
*/
getAge(humanize = true, compact = true, fromNow = false): string | number {
if (fromNow) {
return moment(this.metadata.creationTimestamp).fromNow(); // "string", getTimeDiffFromNow() cannot be used
Expand Down
87 changes: 35 additions & 52 deletions src/renderer/components/+cluster/cluster-issues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Spinner } from "../spinner";
import { ThemeStore } from "../../theme.store";
import { kubeSelectedUrlParam, toggleDetails } from "../kube-detail-params";
import { apiManager } from "../../../common/k8s-api/api-manager";
import { KubeObjectAge } from "../kube-object/age";

export interface ClusterIssuesProps {
className?: string;
Expand All @@ -28,8 +29,8 @@ interface IWarning extends ItemObject {
kind: string;
message: string;
selfLink: string;
age: string | number;
timeDiffFromNow: number;
renderAge: () => React.ReactElement;
ageMs: number;
}

enum sortBy {
Expand All @@ -40,63 +41,42 @@ enum sortBy {

@observer
export class ClusterIssues extends React.Component<ClusterIssuesProps> {
private sortCallbacks = {
[sortBy.type]: (warning: IWarning) => warning.kind,
[sortBy.object]: (warning: IWarning) => warning.getName(),
[sortBy.age]: (warning: IWarning) => warning.timeDiffFromNow,
};

constructor(props: ClusterIssuesProps) {
super(props);
makeObservable(this);
}

@computed get warnings() {
const warnings: IWarning[] = [];

// Node bad conditions
nodesStore.items.forEach(node => {
const { kind, selfLink, getId, getName, getAge, getTimeDiffFromNow } = node;

node.getWarningConditions().forEach(({ message }) => {
warnings.push({
age: getAge(),
getId,
getName,
timeDiffFromNow: getTimeDiffFromNow(),
kind,
message,
selfLink,
});
});
});

// Warning events for Workloads
const events = eventStore.getWarnings();

events.forEach(error => {
const { message, involvedObject, getAge, getTimeDiffFromNow } = error;
const { uid, name, kind } = involvedObject;

warnings.push({
getId: () => uid,
getName: () => name,
timeDiffFromNow: getTimeDiffFromNow(),
age: getAge(),
message,
kind,
selfLink: apiManager.lookupApiLink(involvedObject, error),
});
});

return warnings;
@computed get warnings(): IWarning[] {
return [
...nodesStore.items.flatMap(node => (
node.getWarningConditions()
.map(({ message }) => ({
selfLink: node.selfLink,
getId: node.getId,
getName: node.getName,
kind: node.kind,
message,
renderAge: () => <KubeObjectAge key="age" object={node} />,
ageMs: -node.getCreationTimestamp(),
}))
)),
...eventStore.getWarnings().map(warning => ({
getId: () => warning.involvedObject.uid,
getName: () => warning.involvedObject.name,
renderAge: () => <KubeObjectAge key="age" object={warning} />,
ageMs: -warning.getCreationTimestamp(),
message: warning.message,
kind: warning.kind,
selfLink: apiManager.lookupApiLink(warning.involvedObject, warning),
})),
];
}

@boundMethod
getTableRow(uid: string) {
const { warnings } = this;
const warning = warnings.find(warn => warn.getId() == uid);
const { getId, getName, message, kind, selfLink, age } = warning;
const { getId, getName, message, kind, selfLink, renderAge } = warning;

return (
<TableRow
Expand All @@ -115,7 +95,7 @@ export class ClusterIssues extends React.Component<ClusterIssuesProps> {
{kind}
</TableCell>
<TableCell className="age">
{age}
{renderAge()}
</TableCell>
</TableRow>
);
Expand Down Expand Up @@ -143,15 +123,18 @@ export class ClusterIssues extends React.Component<ClusterIssuesProps> {
return (
<>
<SubHeader className={styles.SubHeader}>
<Icon material="error_outline"/>{" "}
<>Warnings: {warnings.length}</>
<Icon material="error_outline"/> Warnings: {warnings.length}
</SubHeader>
<Table
tableId="cluster_issues"
items={warnings}
virtual
selectable
sortable={this.sortCallbacks}
sortable={{
[sortBy.type]: warning => warning.kind,
[sortBy.object]: warning => warning.getName(),
[sortBy.age]: warning => warning.ageMs,
}}
sortByDefault={{ sortBy: sortBy.object, orderBy: "asc" }}
sortSyncWithUrl={false}
getTableRow={this.getTableRow}
Expand Down
30 changes: 15 additions & 15 deletions src/renderer/components/+config-autoscalers/hpa.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Badge } from "../badge";
import { cssNames } from "../../utils";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { HpaRouteParams } from "../../../common/routes";
import { KubeObjectAge } from "../kube-object/age";

enum columnId {
name = "name",
Expand Down Expand Up @@ -49,17 +50,18 @@ export class HorizontalPodAutoscalers extends React.Component<HorizontalPodAutos
<KubeObjectListLayout
isConfigurable
tableId="configuration_hpa"
className="HorizontalPodAutoscalers" store={hpaStore}
className="HorizontalPodAutoscalers"
store={hpaStore}
sortingCallbacks={{
[columnId.name]: item => item.getName(),
[columnId.namespace]: item => item.getNs(),
[columnId.minPods]: item => item.getMinPods(),
[columnId.maxPods]: item => item.getMaxPods(),
[columnId.replicas]: item => item.getReplicas(),
[columnId.age]: item => item.getTimeDiffFromNow(),
[columnId.name]: hpa => hpa.getName(),
[columnId.namespace]: hpa => hpa.getNs(),
[columnId.minPods]: hpa => hpa.getMinPods(),
[columnId.maxPods]: hpa => hpa.getMaxPods(),
[columnId.replicas]: hpa => hpa.getReplicas(),
[columnId.age]: hpa => -hpa.getCreationTimestamp(),
}}
searchFilters={[
item => item.getSearchFields(),
hpa => hpa.getSearchFields(),
]}
renderHeaderTitle="Horizontal Pod Autoscalers"
renderTableHeader={[
Expand All @@ -81,11 +83,10 @@ export class HorizontalPodAutoscalers extends React.Component<HorizontalPodAutos
hpa.getMinPods(),
hpa.getMaxPods(),
hpa.getReplicas(),
hpa.getAge(),
hpa.getConditions().map(({ type, tooltip, isReady }) => {
if (!isReady) return null;

return (
<KubeObjectAge key="age" object={hpa} />,
hpa.getConditions()
.filter(({ isReady }) => isReady)
.map(({ type, tooltip }) => (
<Badge
key={type}
label={type}
Expand All @@ -94,8 +95,7 @@ export class HorizontalPodAutoscalers extends React.Component<HorizontalPodAutos
expandable={false}
scrollable={true}
/>
);
}),
)),
]}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { limitRangeStore } from "./limit-ranges.store";
import React from "react";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { LimitRangeRouteParams } from "../../../common/routes";
import { KubeObjectAge } from "../kube-object/age";

enum columnId {
name = "name",
Expand All @@ -32,9 +33,9 @@ export class LimitRanges extends React.Component<LimitRangesProps> {
className="LimitRanges"
store={limitRangeStore}
sortingCallbacks={{
[columnId.name]: item => item.getName(),
[columnId.namespace]: item => item.getNs(),
[columnId.age]: item => item.getTimeDiffFromNow(),
[columnId.name]: limitRange => limitRange.getName(),
[columnId.namespace]: limitRange => limitRange.getNs(),
[columnId.age]: limitRange => -limitRange.getCreationTimestamp(),
}}
searchFilters={[
item => item.getName(),
Expand All @@ -51,7 +52,7 @@ export class LimitRanges extends React.Component<LimitRangesProps> {
limitRange.getName(),
<KubeObjectStatusIcon key="icon" object={limitRange}/>,
limitRange.getNs(),
limitRange.getAge(),
<KubeObjectAge key="age" object={limitRange} />,
]}
/>
);
Expand Down
18 changes: 10 additions & 8 deletions src/renderer/components/+config-maps/config-maps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { configMapsStore } from "./config-maps.store";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { ConfigMapsRouteParams } from "../../../common/routes";
import { KubeObjectAge } from "../kube-object/age";

enum columnId {
name = "name",
Expand All @@ -30,16 +31,17 @@ export class ConfigMaps extends React.Component<ConfigMapsProps> {
<KubeObjectListLayout
isConfigurable
tableId="configuration_configmaps"
className="ConfigMaps" store={configMapsStore}
className="ConfigMaps"
store={configMapsStore}
sortingCallbacks={{
[columnId.name]: item => item.getName(),
[columnId.namespace]: item => item.getNs(),
[columnId.keys]: item => item.getKeys(),
[columnId.age]: item => item.getTimeDiffFromNow(),
[columnId.name]: configMap => configMap.getName(),
[columnId.namespace]: configMap => configMap.getNs(),
[columnId.keys]: configMap => configMap.getKeys(),
[columnId.age]: configMap => -configMap.getCreationTimestamp(),
}}
searchFilters={[
item => item.getSearchFields(),
item => item.getKeys(),
configMap => configMap.getSearchFields(),
configMap => configMap.getKeys(),
]}
renderHeaderTitle="Config Maps"
renderTableHeader={[
Expand All @@ -54,7 +56,7 @@ export class ConfigMaps extends React.Component<ConfigMapsProps> {
<KubeObjectStatusIcon key="icon" object={configMap}/>,
configMap.getNs(),
configMap.getKeys().join(", "),
configMap.getAge(),
<KubeObjectAge key="age" object={configMap} />,
]}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { PodDisruptionBudget } from "../../../common/k8s-api/endpoints/podd
import { KubeObjectStatusIcon } from "../kube-object-status-icon";
import type { KubeObjectDetailsProps } from "../kube-object-details";
import { KubeObjectListLayout } from "../kube-object-list-layout";
import { KubeObjectAge } from "../kube-object/age";

enum columnId {
name = "name",
Expand Down Expand Up @@ -42,7 +43,7 @@ export class PodDisruptionBudgets extends React.Component<PodDisruptionBudgetsPr
[columnId.maxUnavailable]: pdb => pdb.getMaxUnavailable(),
[columnId.currentHealthy]: pdb => pdb.getCurrentHealthy(),
[columnId.desiredHealthy]: pdb => pdb.getDesiredHealthy(),
[columnId.age]: pdb => pdb.getAge(),
[columnId.age]: pdb => -pdb.getCreationTimestamp(),
}}
searchFilters={[
pdb => pdb.getSearchFields(),
Expand All @@ -58,18 +59,16 @@ export class PodDisruptionBudgets extends React.Component<PodDisruptionBudgetsPr
{ title: "Desired Healthy", className: "desired-healthy", sortBy: columnId.desiredHealthy, id: columnId.desiredHealthy },
{ title: "Age", className: "age", sortBy: columnId.age, id: columnId.age },
]}
renderTableContents={pdb => {
return [
pdb.getName(),
<KubeObjectStatusIcon key="icon" object={pdb} />,
pdb.getNs(),
pdb.getMinAvailable(),
pdb.getMaxUnavailable(),
pdb.getCurrentHealthy(),
pdb.getDesiredHealthy(),
pdb.getAge(),
];
}}
renderTableContents={pdb => [
pdb.getName(),
<KubeObjectStatusIcon key="icon" object={pdb} />,
pdb.getNs(),
pdb.getMinAvailable(),
pdb.getMaxUnavailable(),
pdb.getCurrentHealthy(),
pdb.getDesiredHealthy(),
<KubeObjectAge key="age" object={pdb} />,
]}
/>
);
}
Expand Down
Loading