Skip to content

Commit

Permalink
Show all dates/times as relative
Browse files Browse the repository at this point in the history
We want to drop the date-fns runtime dependency, and already did so in
all other projects. For consistency, show all dates (such as container
creation, health checks, or image history) as relative times via
`timeformat.distanceToNow()`, with a tooltip that shows the precise
absolute time. This is similar to what we did in machines [1].

Introduce a `RelativeTime` helper component for that, and teach it to
accept both ISO-formatted strings as well as raw timestamps to get rid
of a few repeated `Date.parse()` calls. Also, most time stamps that we
get are in ms (just like `Date` itself), so make it accept that instead
of seconds.

[1] cockpit-project/cockpit-machines@b63a54e6721
  • Loading branch information
martinpitt authored and jelly committed Jul 2, 2024
1 parent 529cea6 commit 39a8dc5
Show file tree
Hide file tree
Showing 7 changed files with 29 additions and 20 deletions.
6 changes: 3 additions & 3 deletions src/ContainerDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const _ = cockpit.gettext;

const render_container_state = (container) => {
if (container.State.Status === "running") {
return cockpit.format(_("Up since $0"), utils.localize_time(Date.parse(container.State.StartedAt) / 1000));
return <><span>{ _("Up since:") } </span><utils.RelativeTime time={container.State.StartedAt} /></>;
}
return cockpit.format(_("Exited"));
};
Expand Down Expand Up @@ -61,15 +61,15 @@ const ContainerDetails = ({ container }) => {
<DescriptionList className='container-details-state'>
<DescriptionListGroup>
<DescriptionListTerm>{_("Created")}</DescriptionListTerm>
<DescriptionListDescription>{utils.localize_time(Date.parse(container.Created) / 1000)}</DescriptionListDescription>
<DescriptionListDescription><utils.RelativeTime time={container.Created} /></DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>{_("State")}</DescriptionListTerm>
<DescriptionListDescription>{render_container_state(container)}</DescriptionListDescription>
</DescriptionListGroup>
{container.State?.Checkpointed && <DescriptionListGroup>
<DescriptionListTerm>{_("Latest checkpoint")}</DescriptionListTerm>
<DescriptionListDescription>{utils.localize_time(Date.parse(container.State.CheckpointedAt) / 1000)}</DescriptionListDescription>
<DescriptionListDescription><utils.RelativeTime time={container.State.CheckpointedAt} /></DescriptionListDescription>
</DescriptionListGroup>}
</DescriptionList>
</FlexItem>
Expand Down
4 changes: 3 additions & 1 deletion src/ContainerHealthLogs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ const ContainerHealthLogs = ({ container, onAddNotification, state }) => {
<span>{log.ExitCode === 0 ? _("Passed health run") : _("Failed health run")}</span>
</Flex>
},
utils.localize_time(Date.parse(log.Start) / 1000)
{
title: <utils.RelativeTime time={log.Start} />
}
],
props: {
key: id,
Expand Down
2 changes: 1 addition & 1 deletion src/ImageHistory.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const ImageDetails = ({ image }) => {
const row = {
columns: [
{ title: IdColumn(layer.Id), props: { className: "ignore-pixels" } },
{ title: utils.localize_time(layer.Created), props: { className: "ignore-pixels" } },
{ title: <utils.RelativeTime time={layer.Created * 1000} />, props: { className: "ignore-pixels" } },
{ title: layer.CreatedBy, props: { className: "ignore-pixels" } },
{ title: cockpit.format_bytes(layer.Size), props: { className: "ignore-pixels" } },
{ title: layer.Comment, props: { className: "ignore-pixels" } },
Expand Down
2 changes: 1 addition & 1 deletion src/Images.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class Images extends React.Component {
const columns = [
{ title: utils.image_name(image), header: true, props: { modifier: "breakWord" } },
{ title: image.isSystem ? _("system") : <div><span className="ct-grey-text">{_("user:")} </span>{this.props.user}</div>, props: { className: "ignore-pixels", modifier: "nowrap" } },
{ title: utils.localize_time(image.Created), props: { className: "ignore-pixels" } },
{ title: <utils.RelativeTime time={image.Created * 1000} />, props: { className: "ignore-pixels" } },
{ title: utils.truncate_id(image.Id), props: { className: "ignore-pixels" } },
{ title: cockpit.format_bytes(image.Size), props: { className: "ignore-pixels", modifier: "nowrap" } },
{ title: <span className={usedByCount === 0 ? "ct-grey-text" : ""}>{usedByText}</span>, props: { className: "ignore-pixels", modifier: "nowrap" } },
Expand Down
4 changes: 2 additions & 2 deletions src/PruneUnusedContainersModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import cockpit from 'cockpit';
import { ListingTable } from 'cockpit-components-table.jsx';

import * as client from './client.js';
import * as utils from './util.js';
import { RelativeTime } from './util.js';

const _ = cockpit.gettext;

Expand All @@ -18,7 +18,7 @@ const getContainerRow = (container, userSystemServiceAvailable, user, selected)
props: { width: 25, },
},
{
title: utils.localize_time(Date.parse(container.created) / 1000),
title: <RelativeTime time={container.created} />,
props: { width: 20, },
},
];
Expand Down
20 changes: 13 additions & 7 deletions src/util.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { useContext } from "react";

import { debounce } from 'throttle-debounce';
import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip";

import cockpit from 'cockpit';
import * as timeformat from 'timeformat';

import { debounce } from 'throttle-debounce';
import * as dfnlocales from 'date-fns/locale';
import { formatRelative } from 'date-fns';
const _ = cockpit.gettext;

export const PodmanInfoContext = React.createContext();
Expand Down Expand Up @@ -39,10 +40,15 @@ export function truncate_id(id) {
return id.substr(0, 12);
}

export function localize_time(unix_timestamp) {
const locale = (cockpit.language == "en") ? dfnlocales.enUS : dfnlocales[cockpit.language.replace('_', '')];
return formatRelative(unix_timestamp * 1000, Date.now(), { locale });
}
// this supports formatted strings (via Date.parse) or raw timestamps
export const RelativeTime = ({ time }) => {
if (!time)
return null;
const timestamp = typeof time === "string" ? Date.parse(time) : time;
const dateRel = timeformat.distanceToNow(timestamp, true);
const dateAbs = timeformat.dateTimeSeconds(timestamp);
return <Tooltip content={dateAbs}><span>{dateRel}</span></Tooltip>;
};

/*
* The functions quote_cmdline and unquote_cmdline implement
Expand Down
11 changes: 6 additions & 5 deletions test/check-application
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ class TestApplication(testlib.MachineCase):
b.wait_in_text(f"{first_row_sel} td[data-label=\"ID\"]",
images[IMG_HELLO_LATEST][:12])
created_sel = f"{first_row_sel} td[data-label=\"Created\"]"
b.wait_in_text(f"{created_sel}", "today at")
b.wait_text(f"{created_sel}", "less than a minute ago")
# topmost (last) layer
created_sel = f"{first_row_sel} td[data-label=\"Created by\"]"
b.wait_in_text(f"{created_sel}", "COPY")
Expand Down Expand Up @@ -848,7 +848,7 @@ class TestApplication(testlib.MachineCase):
b.wait_not_in_text("#containers-images", "<none>:<none>")
b.click(".listing-action button:contains('Show intermediate images')")
b.wait_in_text("#containers-images", "<none>:<none>")
b.wait_in_text("#containers-images tbody:last-child td[data-label=Created]", "today at")
b.wait_text("#containers-images tbody:last-child td[data-label=Created]", "less than a minute ago")

b.click(".listing-action button:contains('Hide intermediate images')")
b.wait_not_in_text("#containers-images", "<none>:<none>")
Expand Down Expand Up @@ -1522,9 +1522,9 @@ class TestApplication(testlib.MachineCase):

if self.has_criu:
b.wait(lambda: self.getContainerAttr("swamped-crate", "State") in NOT_RUNNING)
b.wait_in_text(
b.wait_text(
f'#containers-containers tr:contains("{IMG_BUSYBOX}") dt:contains("Latest checkpoint") + dd',
'today at'
'less than a minute ago'
)
else:
# expect proper error message
Expand Down Expand Up @@ -2005,7 +2005,8 @@ class TestApplication(testlib.MachineCase):

self.toggleExpandedContainer(IMG_BUSYBOX)

b.wait_in_text(f'#containers-containers tr:contains("{IMG_BUSYBOX}") dt:contains("Created") + dd', 'today at')
b.wait_text(f'#containers-containers tr:contains("{IMG_BUSYBOX}") dt:contains("Created") + dd',
'less than a minute ago')

b.click(".pf-m-expanded button:contains('Integration')")

Expand Down

0 comments on commit 39a8dc5

Please sign in to comment.