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

Implement listing images/containers using React JSX #3

Merged
merged 1 commit into from
Aug 20, 2018

Conversation

QiWang19
Copy link
Contributor

Signed-off-by: Qi Wang qiwan@redhat.com

@QiWang19
Copy link
Contributor Author

@martinpitt PTAL

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.

Great work already! I reviewed roughly a third of this PR, but a lot of comments apply to several places in the code. Also, this needs to grow integration tests along with the code, to ensure it is (and stays) working (also with newer podman versions).

class ContainerDetails extends React.Component {
constructor(props) {
super(props);
}
Copy link
Member

Choose a reason for hiding this comment

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

This component has no state, so it should be a functional component. (another interesting blog post). Also, it's custom to explicitly spell out the properties that you support/expect, like this:

const ContainerDetails({container}) => (
    <div> ...
        <dt>{ container.Created.. }</dt>
    </div>
);

package.json Outdated
"bootstrap-less": "^3.3.8",
"create-react-class": "^15.6.3",
"exenv": "^1.2.2",
"jquery": "^3.3.1",
Copy link
Member

Choose a reason for hiding this comment

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

Please avoid introducing jQuery in new code, and particularly not with react. I don't see it actually being used here? I think this can just be dropped. Likewise, please comb the list for other unused stuff, like "exenv" -- it just unnecessarily enlarges npm install time, and all of these dependencies introduce maintenance. react-create-class also sounds like dead weight -- use proper ES6 classes instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed it. But dockerUI uses Id.

return (
<div className='listing-ct-body'>
<dl>
<dt>{_("Id")} </dt> <dd>{ container.ID }</dd>
Copy link
Member

Choose a reason for hiding this comment

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

Please avoid the extra whitespace in the element. Perhaps they are not rendered, but it might still cause some layout issues. Also please be consistent with putting the <dd>s into a new line.

"Id" should be spelled "ID", as it's an acronym.

class Containers extends React.Component {
constructor(props) {
super(props);
console.log(props.containers);
Copy link
Member

Choose a reason for hiding this comment

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

Debugging leftover.

if (state.Status)
return state.Status;
if (state.Running)
return "running";
Copy link
Member

Choose a reason for hiding this comment

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

Needs i18n (_("") wrapper)

}

render() {
console.log(this.props.onlyShowRunning);
Copy link
Member

Choose a reason for hiding this comment

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

debugging leftover

//TODO
let emptyCaption = _("No containers");

let filtered = this.props.containers.filter((container)=>{
Copy link
Member

Choose a reason for hiding this comment

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

Missing spaces around operator, and unnecessary braces around a single argument.

let filtered = this.props.containers.filter((container)=>{
if (this.props.onlyShowRunning && !container.State.Running)
return false;
return true;
Copy link
Member

Choose a reason for hiding this comment

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

write this more succinctly as `filter(container => (!this.props.onlyShowRunning || container.State.Running));

constructor(props) {
super(props);

}
Copy link
Member

Choose a reason for hiding this comment

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

Unnecessary if you don't do anything other than calling the parent. Also, this component can be functional, it doesn't have any state.

return (
<Modal
isOpen={this.props.show}
>
Copy link
Member

Choose a reason for hiding this comment

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

IMHO this looks really ugly - just write it as one line, it's short enough.

@QiWang19
Copy link
Contributor Author

I fixed the review issues you found. @martinpitt PTAL

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, nice progress! Still a bunch of small issues, and two large ones:

  • This still does not update the tests, they fail now. It's acceptable to not cover all the new functionality with tests right away, this can happen in follow-up PRs (but must happen nevertheless). But they at least must succeed.

  • Please use consistent indentation. 8 spaces seems rather excessive and makes a lot of code rather "narrow". We use 4 everywhere else.

src/index.html Outdated
<link rel="stylesheet" href="../base1/patternfly.css">
<link href="docker.css" type="text/css" rel="stylesheet" id="term-style">
Copy link
Member

Choose a reason for hiding this comment

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

Please rename this to podman.css (same for the file) to avoid confusion.

"",
// TODO: MemoryUsage, MemoryLimit
"",
state,
Copy link
Member

Choose a reason for hiding this comment

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

This should still be i18n'ed, but I'm happy for this to be a // TODO: i18n comment for now.

let columns = [
{ name: container.Name, header: true },
image,
container.Config.Entrypoint === "" ? container.Config.Cmd.join(" ") : container.Config.Cmd.join(""),
Copy link
Member

Choose a reason for hiding this comment

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

The second join is still confusing, please add a comment what it does.

{
test: /\.es6$/,
loader: "babel-loader"
},
Copy link
Member

Choose a reason for hiding this comment

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

These all already exist above and are duplicated.

@@ -0,0 +1,33 @@
import React from 'react';
Copy link
Member

Choose a reason for hiding this comment

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

This file must be named .jsx, to apply correct babel and eslint rules. It's not "classic" non-ES6 JavaScript. Same applies to the other *.js files in this PR.

{
test: /views\/[^\/]+\.html$/,
loader: "ng-cache?prefix=[dir]"
},
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 unrelated and should be a separate commit with a proper justification.

},
{
test: /[\/]angular\.js$/,
loader: "exports?angular"
}
]
Copy link
Member

Choose a reason for hiding this comment

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

There's no angular in this code, and you should stay really far away from introducing it :-) Please drop this.

import cockpit from 'cockpit';
import Select from '../lib/cockpit-components-select.jsx';

const _ = cockpit.gettext;cockpit.gett
Copy link
Member

Choose a reason for hiding this comment

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

There's some junk here

filterText: ''
};

this.filterChanged = this.filterChanged.bind(this);
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't seem necessary, you only call this from a method that already has a correct this.


handleFilterChange (value) {
this.setState({ filter: value });
this.props.onChange(value);
Copy link
Member

Choose a reason for hiding this comment

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

Should check if the onChange property is actually defined. Or declare it a mandatory property.

@QiWang19 QiWang19 force-pushed the images branch 2 times, most recently from f3c2e83 to d529ae8 Compare August 2, 2018 17:30
@QiWang19
Copy link
Contributor Author

QiWang19 commented Aug 2, 2018

@martinpitt I have made the changes you requested. Sorry, I am still getting used to the github workflow.

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.

Some more issues inline, also you still didn't update the tests. Please make sure that make check works.

But this PR really isn't working like that, so some things about development and review in general:

  • Don't add more and more new features (like the vuln scanning) to this single PR. It is already immensely large and really difficult to review, and at this rate it gets worse faster than it gets better.

  • You should do lots of small PRs, each of which implement one self-contained new feature or bug fix, together with comprehensive integration tests. A small PR is much easier and faster to review and understand, both by you and the reviewer.

For example, you could do one PR to move the varlink interface into a new utils.es6, one PR to add a container list, one PR to add an image list, etc.

  • Finish one PR and get it landed before working on new stuff. If you don't finish the old one, nothing will land at the end.

This isn't meant to be offensive or discouraging, I just want to guide you how you can actually land changes effectively.

Thanks! FYI, I'll be on PTO the next two weeks, so please ask in #cockpit for reviewers.

container.Config.Entrypoint === "" ? container.Config.Cmd.join(" ") : container.Config.Cmd.join(""),
container.State.Running ? utils.format_cpu_percent(container.HostConfig.CpuPercent) : "",
containerStats ? utils.format_memory_and_limit(containerStats.mem_usage, containerStats.mem_limit) : "",
state,
Copy link
Member

Choose a reason for hiding this comment

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

As I said, please add a TODO: i18n comment here.

let columns = [
{ name: container.Name, header: true },
image,
//Concat the cmd directly if has entrypoint, otherwise join cmd with space.
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't really explain anything, it's just translating JS to English. It's quite clear what the code does. It's also clear why we want to join the container entrypoint command array with spaces. But it needs to be explained why the config command does not get spaces without an entry point. It's still a comand array, isn't it? How does that not look broken?

{
test: /\.jsx$/,
loader: "babel-loader"
},
Copy link
Member

Choose a reason for hiding this comment

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

This is still duplicated from above. You shouldn't/can't have to loaders for .js and .jsx. You need to merge the strict and babel loader into one list, if you really need both.

{
test: /\.less$/,
loader: extract.extract("css-loader?sourceMap&minimize=!less-loader?sourceMap&compress=false")
},
Copy link
Member

Choose a reason for hiding this comment

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

Why do you need the css and less handlers? For some node dependencies? That usually isn't required in your own project (but I could miss something there)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because I used cockpit-components-listing.jsx from docker UI, it import the less

filter: 'running',
filterText: ''
};
this.filterChanged = this.filterChanged.bind(this);
Copy link
Member

Choose a reason for hiding this comment

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

Already from the previous review, but it now got hidden; This doesn't seem necessary, you only call this from a method that already has a correct this.

src/Images.jsx Outdated
this.handleCancelImageDeleteModal = this.handleCancelImageDeleteModal.bind(this);
this.handleRemoveImage = this.handleRemoveImage.bind(this);
this.handleCancelImageRemoveError = this.handleCancelImageRemoveError.bind(this);
this.handleForceRemoveImage = this.handleForceRemoveImage.bind(this);
Copy link
Member

Choose a reason for hiding this comment

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

Please don't just wholesale bind all of your methods. This is only required if you use them as handler attributes in a HTML tag, like <button onClick={this.myHandler}. As soon as you call it from one of the class'es methods or even from an arrow method (<button onClick={() => this.myHandler(...)) you don't need this bind. E. g. at least renderRow() and navigateToImage() are unnecessary binds.

(This is a nitpicking of course, and doesn't really hurt - but it makes the code longer, and it's good to understand what this actually does).

src/Images.jsx Outdated
}

showRunImageDialog(e) {
e.preventDefault()
Copy link
Member

Choose a reason for hiding this comment

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

preventDefault is a rather large axe, especially if it's unconditional for all events. This always needs a comment that justifies why it's necessary.

src/app.jsx Outdated
@@ -21,110 +21,149 @@
import cockpit from 'cockpit';
import React from 'react';
import './app.scss';
Copy link
Member

Choose a reason for hiding this comment

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

Either drop src/app.scss and this import, or move your podman.scss definitions in there. Don't keep two, that's confusing.

src/index.html Outdated
@@ -23,6 +23,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">

<link rel="stylesheet" href="../base1/patternfly.css">
<link href="podman.css" type="text/css" rel="stylesheet" id="term-style">
Copy link
Member

Choose a reason for hiding this comment

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

Please don't do this. Nothing in the .html file itself should need the CSS, only the App JSX object. Import it in src/app.jsx like it currently imports app.scss (see above, there should only be one file).

@QiWang19 QiWang19 force-pushed the images branch 7 times, most recently from 47d8f40 to bb15d9c Compare August 8, 2018 16:04
@QiWang19
Copy link
Contributor Author

QiWang19 commented Aug 8, 2018

@martinpitt @garrett @andreasn @stefwalter @larskarlitski I've added some tests. PTAL.

@garrett
Copy link
Member

garrett commented Aug 9, 2018

@QiWang19: Hey! I saw your request in IRC. I'm replying here to let you know you were heard. 😉

If you have design questions or some HTML and/or CSS questions, I'm happy to help. I even know JavaScript pretty OK. However, I don't know React/JSX... outside of modifying it to make HTML/CSS/JS a bit better.

As your PR looks like a lot of JSX, I probably can't help at this time in this particular PR.

That all said, I've been a fan of Podman (and everything related, like Buildah) for a while and I'm really looking forward to having Podman support in Cockpit!

@martinpitt
Copy link
Member

Back from PTO, having a look. Tests on master started failing with

Failed to enable unit: Unit file io.projectatomic.podman.socket does not exist.

as podman apparently renamed the socket. This will also break this PR.

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.

After PR #4 to fix the socket path this now needs a rebase, and applying the socket renaming to your branch. I also found a few small issues which I am fixing myself.

The test also fails:

  File "test/check-application", line 40, in testBasic
    b.wait_visible('#containers-containers tr:contains("busybox:latest")')
  File "test/common/testlib.py", line 285, in wait_visible
    return self.wait_js_func('ph_is_visible', selector)
  File "test/common/testlib.py", line 270, in wait_js_func
    self.wait_js_cond("%s(%s)" % (func, ','.join(map(jsquote, args))))
  File "test/common/testlib.py", line 267, in wait_js_cond
    self.raise_cdp_exception("timeout\nwait_js_cond", cond, result["exceptionDetails"], trailer)
  File "test/common/testlib.py", line 167, in raise_cdp_exception
    raise Error("%s(%s): %s" % (func, arg, msg))
testlib.Error: timeout
wait_js_cond(ph_is_visible("#containers-containers tr:contains(\"busybox:latest\")")): #containers-containers tr:contains("busybox:latest") not found

I am going to investigate this, and then force-push your branch, to unblock this.

.gitignore Outdated
@@ -13,3 +13,5 @@ test/common/
test/images/
*.pot
POTFILES*
tmp/*
/src/test.jsx
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 like a debugging leftover. Don't add such local files to .gitignore, you are going to forget about them.

# create a container
m.execute("podman run -d --name swamped-crate busybox sleep 1000")
# m.execute("podman run -d --name swamped-crate alpine sleep 10000")
Copy link
Member

Choose a reason for hiding this comment

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

Leftover stuff, should be dropped.


# FIXME: UI needs to listen to change signals and update automatically
b.reload()
Copy link
Member

Choose a reason for hiding this comment

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

This reload should now be dropped as well.

test/vm.install Outdated
@@ -15,3 +15,4 @@ fi
# grab a few images to play with; tests run offline, so they cannot download images
podman pull busybox
podman pull docker.io/alpine
# podman rm eager_murdock
Copy link
Member

Choose a reason for hiding this comment

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

Leftover.

b.wait_visible('#containers-images tbody tr:contains("busybox:latest") + tr button.btn-delete')

# show all containers to interact with stopped ones
m.execute("podman run -d --name swamped-crate busybox sh")
Copy link
Member

Choose a reason for hiding this comment

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

This cannot work, the container will exit immediately. This is why in my initial tests I ran sleep 10000 in it.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I see, it's supposed to be in the "exited" state, as you switch to "everything".

martinpitt pushed a commit to QiWang19/cockpit-podman that referenced this pull request Aug 20, 2018
@martinpitt
Copy link
Member

I did an initial force push to fix the conflict and make the test actually work. Note that I had to put back the b.reload(), as the UI does not react to container changes. But I'm going to push again, there are tons of tabs and wrong indentations in your .jsx files (JS uses 4 space indentation, and no tabs). There are also still some earlier review comments which still apply.

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.

I did another force push to fix the tab damage, and a few other minor issues. Let's land this now, to unblock this.

Please do a follow-up PR with completing the tests. For example:

  • Start a running container and make sure that it appears so.
  • Check that container names and container commands are shown correctly. Particularly with commands that have more than one word, to cover correct spacing.
  • Cover the "Commit", "Delete", and "Start" buttons in container details. The latter currently causes a crash, this needs to be fixed as well.
  • Image details and deletion.

@martinpitt
Copy link
Member

Tests succeeded, landing. Thanks!

@martinpitt martinpitt merged commit d847a2f into cockpit-project:master Aug 20, 2018
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.

3 participants