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

Clean up and type number formatting APIs #20334

Merged
merged 7 commits into from
Apr 19, 2024

Conversation

allisonkarlitskaya
Copy link
Member

  • move a very specific API (cockpit.get_byte_units()) with a single user (storaged) into that user (along with its unit tests), and simplify it a bit
  • add a new API style for our number formatting APIs that removes many of the existing warts
  • port test-format to TypeScript

pkg/lib/cockpit.d.ts Fixed Show fixed Hide fixed
@allisonkarlitskaya
Copy link
Member Author

The content of this PR is basically my best crack at #20322 (comment)

I'd like to actually break the API, given that there are zero known users; but I'm too scared about the unknown ones. So, my gut feeling:

  • Design the API as we want it (see my proposal above, I'm happy to discuss of course)

  • Do the types exactly for this, so that we learn about unintended API usage via type checks

  • Then add some backwards compat hacks inside of format_bytes() to opportunistically interpret a non-object second argument: if it's "1024", treat it as { binary: true }, and ignore all others. console.warn() about it in any case.

Is that possible/reasonable?

Copy link
Member

@martinpitt martinpitt left a comment

Choose a reason for hiding this comment

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

Thanks for figuring this out! Aside from a few implementation fixmes this goes into a good direction. I'd like to clean it up more, but that doesn't have to block this PR, it should be a follow-up.

pkg/storaged/utils.js Outdated Show resolved Hide resolved
pkg/storaged/utils.js Outdated Show resolved Hide resolved
if (!is_object(options))
options = { separate: options };

options.factor = second_arg;
Copy link
Member

Choose a reason for hiding this comment

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

This looks unclean: If options is already an object, it is passed in from the caller. This will modify that passed-in argument. So as an intermediate step, this is safer: options = {...options, factor: second_arg} (i.e. make a copy).

But even more so this is a field which isn't documented or covered by types, so doesn't this raise some ts alarm?

WDYT about something like

if (second_arg === 1024)
    options = {...options, base2: true }
else
   console.warn("format_units(): Ignoring invalid factor value", second_arg);

?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks. This is a good suggestion and I'm surprised I was sloppy about this.

pkg/lib/cockpit.js Show resolved Hide resolved
pkg/lib/cockpit.js Outdated Show resolved Hide resolved
pkg/lib/cockpit.js Outdated Show resolved Hide resolved
function format_number(n: MaybeNumber, precision?: number): string
function format_bytes(n: MaybeNumber, options?: FormatOptions): string;
function format_bytes_per_sec(n: MaybeNumber, options?: FormatOptions): string;
function format_bits_per_sec(n: MaybeNumber, options?: FormatOptions & { base2?: false }): string;
Copy link
Member

Choose a reason for hiding this comment

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

Hmmm, so the argument here is. if I wanted to provide an options argument, I'd have to figure out the default factor which would be error prone?

Copy link
Member Author

Choose a reason for hiding this comment

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

The new API basically has two possibilities: SI units (default, old factor=1000) and IEC units (old factor=1024).

Copy link
Member

Choose a reason for hiding this comment

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

@jelly With the "old" API you could specify undefined for the factor, in which case it would be the default "1000". But indeed this is error prone -- in fact, my first version here gave null, which made it fall over completely. That's why I'd like to get rid of that.

This has a single user (in storaged) and is more general than it needs
to be to support that user.  Hardcode it to SI-based units and move it
into storaged/utils.js, so we can keep the unit tests.

The unit tests themselves only test the 1024 case (which is the opposite
of what is actually used in practice) so adjust them for SI (mostly by
renaming variables and changing `1024`s to `1000`s).
@martinpitt
Copy link
Member

these failures are actually relevant -- first of all, a unit conversion got lost along the way (see screenshot), and now I agree that we we need to restrict (or disable) the cockpit.warning(). Sorry for the bad advice.

Thanks to Martin for some ideas there.
Add a small function called f() to qunit-tests.ts.  It is intended to be
used as a tagged template string format function.

The default formatting for template strings passes each argument to
String(), which means that objects get formatted like:

  [object Object]

This function formats them using JSON, so you can see the actual object
value.

It can be imported into any QUnit test and used like:

  f`format_bytes_per_sec(${checks[i][0]}, ${checks[i][1]}, ${checks[i][2]})`

which produces output like:

  ok 121 - format_bytes_per_sec: format_bytes_per_sec(25555678, "kB/s", {"precision":2})
Use our fancy new tagged templates to name our assertions in
test-format.  In addition to being a lot easier (and shorter) to write,
it means we see `{"precision": 2}` instead of `[object Object]`.

Also, leave out the value that we're comparing it equal to.  This value
is already displayed on assertion failures, and having it shown twice
only makes the output harder to read.
@allisonkarlitskaya
Copy link
Member Author

allisonkarlitskaya commented Apr 18, 2024

these failures are actually relevant -- first of all, a unit conversion got lost along the way (see screenshot)

This comes down to

export function format_fsys_usage(used, total) {
    let text = "";
    let parts = cockpit.format_bytes(total, undefined, { separate: true, precision: 2 });
    text = " / " + parts.join(" ");
    const unit = parts[1];

    parts = cockpit.format_bytes(used, unit, { separate: true, precision: 2 });
    return parts[0] + text;
}

ie: another API doing the same thing as the usage you were trying to eliminate from podman in cockpit-project/cockpit-podman#1673. cc @garrett

Let's make sure we don't regress this with changes to
`cockpit.format_units()`.
Add a new simplified style for all of the units-based number formatting
APIs (bytes, bytes/sec, bits/sec).  Keep loose versions of the old APIs
around in `cockpit.d.ts`, but mark them deprecated.

Make sure we test the new API variant from test-format.js.
This was pretty much completely pain-free, with a minor exception: the
test overrides the `cockpit.language` attribute, which we would
otherwise probably declare as `const`.
Copy link
Member

@martinpitt martinpitt left a comment

Choose a reason for hiding this comment

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

Brilliant, thanks! /devel failed with an unexpected message in TestTerminal.testBasic ("truncated data in external channel"), but I think that's an unrelated flake. The weather report shows the same message in TestSuperuser.testBasic here and here.

I retry it to be sure.

@jelly can you please also check this again?

pkg/lib/qunit-tests.ts Show resolved Hide resolved
function format_number(n: MaybeNumber, precision?: number): string
function format_bytes(n: MaybeNumber, options?: FormatOptions): string;
function format_bytes_per_sec(n: MaybeNumber, options?: FormatOptions): string;
function format_bits_per_sec(n: MaybeNumber, options?: FormatOptions & { base2?: false }): string;
Copy link
Member

Choose a reason for hiding this comment

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

Just to make sure I understand this: The base2 restriction is here to make base2: true fall through the "deprecated" declaration, right? Not because enabling base2 would actually change the output type.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm pretty sure that enabling base2 will break this (ie: return just the number) in exactly the same way as passing 1024 as factor would. That particular function lacks base2 units (ie: no Mibps or so)...

Copy link
Member

Choose a reason for hiding this comment

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

Right, and I don't think we ever supported that actually.

@martinpitt martinpitt requested a review from jelly April 19, 2024 05:44
@@ -1,5 +1,5 @@
import cockpit from "cockpit";
import QUnit from "qunit-tests";
import QUnit, { f } from "qunit-tests";
Copy link
Member

Choose a reason for hiding this comment

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

What is this f? This aint no Python

// compat API: we used to accept 'factor' as a separate second arg
if (third_arg || (second_arg && !is_object(second_arg))) {
if (!deprecated_format_warned) {
console.warn(`cockpit.format_{bytes,bits}[_per_sec](..., ${second_arg}, ${third_arg}) is deprecated.`);
Copy link
Member

Choose a reason for hiding this comment

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

When do we remove it, should we include that? Like in 10 releases this API is gone?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm OK with next release already...

@allisonkarlitskaya allisonkarlitskaya merged commit 850feb2 into cockpit-project:main Apr 19, 2024
80 checks passed
@allisonkarlitskaya allisonkarlitskaya deleted the format-ts branch April 19, 2024 08:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants