Skip to content

Conversation

carlosms
Copy link
Contributor

@carlosms carlosms commented Mar 2, 2018

Part of #160, this PR will add a new route to create & edit experiments, and a way to upload a new SQLite DB with file pairs.

@smacker smacker mentioned this pull request Mar 2, 2018
5 tasks
}

const mapStateToProps = state => ({
user: state.user,
Copy link
Contributor

Choose a reason for hiding this comment

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

looks like you don't use it anywhere

export const initialState = {
loading: true,
error: null,
createOnProgress: false,
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't it be in progress?


class ExperimentsAdmin extends Component {
componentDidMount() {
this.props.load();
Copy link
Contributor

Choose a reason for hiding this comment

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

no need to fix it, just to let you know. Here we have kind of "bug" here. Because user will come here from another page in most of the cases we will have experiments in the store already. So a user will see the list of experiments and in few milliseconds it will be replaced by a loader and then by list again.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was actually copied from Experiments.js.
I guess one way to handle this would be to have an expiration date for the data, and then the function could load only if the list is empty, or the expiration date passed... But for now in my opinion a forced load is not that inconvenient for the user experience.

return api
.createExperiment(name, description)
.then(() => {
dispatch({ type: CREATE_SUCCESS });
Copy link
Contributor

Choose a reason for hiding this comment

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

no need to fix. Just let you know, an optimistic update would fit nicely here.

@carlosms
Copy link
Contributor Author

carlosms commented Mar 9, 2018

This is how it looks, based on the wireframes provided by @ricardobaeta here #160 (comment), but keeping the name 'experiment' for consistency with the rest of the app.

peek 2018-03-09 19-36

@dpordomingo
Copy link
Contributor

Conflicts appeared :(

Copy link
Contributor

@smacker smacker left a comment

Choose a reason for hiding this comment

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

some comments.
also tests are missed.

uploadResult: UPLOAD_RES_NONE,

editModalShow: false,
editModalExpID: null,
Copy link
Contributor

Choose a reason for hiding this comment

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

could you please keep names consistent with all other js files?
In js in general and in code-annotation frontend camelCase is a standard.

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 must be missing something, sorry, can you paste me the exact change you have in mind?

Copy link
Contributor

Choose a reason for hiding this comment

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

editModalExpID -> editModalExpId. Just because it's IdnotID` everywhere.

export const UPLOAD = 'ca/experiments/UPLOAD';
export const UPLOAD_SUCCESS = 'ca/experiments/UPLOAD_SUCCESS';
export const UPLOAD_ERROR = 'ca/experiments/UPLOAD_ERROR';
export const OPEN_EDIT_MODAL = 'ca/experiments/OPEN_EDIT_MODAL';
Copy link
Contributor

Choose a reason for hiding this comment

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

usually, it's a good idea to keep store agnostic to presentation level.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To be honest I didn't like that much the previous code, but I didn't want to spend much more time refactoring it. I've tried to improve it in ce357ff, moving the modal state to Experiments.js

this.state.nameVal,
this.state.descriptionVal
);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

please follow early return rule as all other js code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed in 0fb33e0

Can you please point me to a guideline (like the airbnb one we use in eslint.rb) where this rule is explained?

Copy link
Contributor

Choose a reason for hiding this comment

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

the rule usually you look how the current code is written and do the same.

there is a rule about no-else return in airbnb:
https://github.com/airbnb/javascript#blocks--no-else-return

no always early return because sometimes it makes sense to not return early (though it was there before)

you can read more why people do that by simple googling early return. It's very popular pattern in javascript. Java style of else everywhere is much less common.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, thanks for clarifying. I'm aware of the rules you mention, but I just didn't see them applying here and wanted to check if you were talking about something else. From my point of view the code was:

if (condition)
    somehting()
else
    other()
end

// Posibly some more code

Now I understand your point of view, as there was no more code after the else.

}

componentWillReceiveProps(nextProps) {
const exp = nextProps.editModalExperiment;
Copy link
Contributor

Choose a reason for hiding this comment

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

I see this code 2 times. The copy-past itself is not a problem. But component depends on both constructor and componentWillReceiveProps. So any the change must be done in both places. It would be much better to extract this code to function which returns state. And do this.state = fn(props) in constructor and this.setState(fn(nextProps)) here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense, it's an easy improvement. Changed in dd9e4fb


componentWillReceiveProps(nextProps) {
const exp = nextProps.editModalExperiment;
if (exp !== undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

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

the check should compare nextProps with current props. If you need more info, come over and I can explain it verbally.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed in dd9e4fb


let uploadIcon;
let uploadClass;
if (uploadInProgress) {
Copy link
Contributor

Choose a reason for hiding this comment

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

looks like switch will be better fit here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you sure? The chained if's condition use two different variables, I don't think it will be cleaner with a switch.

Copy link
Contributor

Choose a reason for hiding this comment

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

and ifelse switch would be clearer, but I'd not block this wonderful work for this cosmetic suggestion 😉

if (uploadInProgress) { /* something */ }
else switch (uploadResult) {
    case UPLOAD_RES_SUCCESS: /* something */ break;
    case UPLOAD_RES_FAILURE: /* something */ break;
    default: /* something */
}

}

return (
<Modal show={editModalShow} onHide={this.handleClose}>
Copy link
Contributor

Choose a reason for hiding this comment

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

I would recommend controlling show outside of the modal component. In general it's better to do editModalShow && <Modal>. Otherwise it might not render virtual/real dom if the underlying component is written well, but all life cycle methods would be executed on every parent update.
Not very important in this particular case though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. I decided to leave it as a property of the Modal component to avoid introducing more and more changes. Let me know if you think this should be changed considering the last changes.

.then(res => {
dispatch({ type: CREATE_SUCCESS });
dispatch(load());
dispatch(showEditModal(true, res.id));
Copy link
Contributor

Choose a reason for hiding this comment

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

here is a good example why it's better to keep store view agnostic. create action can be called from anywhere. But it will set the modal show to true. It is super confusing and can cause weird bugs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed in ce357ff

@ricardobaeta
Copy link
Contributor

Thank you @carlosms :) It looks great!

@carlosms
Copy link
Contributor Author

@dpordomingo I rebased and there are no more conflicts (I was avoiding it to not loose the commit hashes).

@smacker feel free to give it another pass. I was delaying making the tests to wait for change requests, I'll upload them shortly.

export const LOAD_SUCCESS = 'ca/experiments/LOAD_SUCCESS';
export const LOAD_ERROR = 'ca/experiments/LOAD_ERROR';
export const SET = 'ca/experiments/SET';
export const CREATE = 'ca/experiments/CREATE';
Copy link
Contributor

Choose a reason for hiding this comment

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

it's not really wrong, but kind of weird. You can see how it's done in redux usually in official examples: https://github.com/reactjs/redux/tree/master/examples/todomvc/src

@dpordomingo dpordomingo changed the title [WIP] Experiments management, frontend code Experiments management, frontend code Mar 12, 2018
@dpordomingo dpordomingo changed the title Experiments management, frontend code Manage Experiments from the front Mar 12, 2018
@dpordomingo dpordomingo mentioned this pull request Mar 12, 2018
12 tasks
this.props.onOpenEdit(expId);
});

return;
Copy link
Contributor

Choose a reason for hiding this comment

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

not required here, but usually it's a good idea when you have a promise to return it. For example, you would need it for tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done 👍

if (create) {
this.props
.experimentCreate(this.state.nameVal, this.state.descriptionVal)
.then(expId => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I usually put such logic in dispatchToProps, to keep code of the component abstract. Not a big problem though.

Object {
"createInProgress": false,
"error": "test error",
"initialState": Object {
Copy link
Contributor

Choose a reason for hiding this comment

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

it's incorrect.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I missed the spread operator while c&p and of course the tests were not failing 😄

exports[`experiments/reducer CREATE_SUCCESS 1`] = `
Object {
"createInProgress": false,
"initialState": Object {
Copy link
Contributor

Choose a reason for hiding this comment

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

it's incorrect.

exports[`experiments/reducer UPDATE_ERROR 1`] = `
Object {
"error": "test error",
"initialState": Object {
Copy link
Contributor

Choose a reason for hiding this comment

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

it's incorrect.


exports[`experiments/reducer UPDATE_SUCCESS 1`] = `
Object {
"initialState": Object {
Copy link
Contributor

Choose a reason for hiding this comment

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

it's incorrect.

if (create) {
this.props
.experimentCreate(this.state.nameVal, this.state.descriptionVal)
.then(expId => {
Copy link
Contributor

Choose a reason for hiding this comment

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

in case of error here will be undefined and this.props.onOpenEdit will fail

Copy link
Contributor

@smacker smacker left a comment

Choose a reason for hiding this comment

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

  • tests are incorrect
  • found 1 bug
  • few suggestions (not required, up to you)

const errText = 'some error';
fetch.mockReject(errText);

store.dispatch(create('new_name', 'new_desc')).catch(() => {
Copy link
Contributor

@smacker smacker Mar 14, 2018

Choose a reason for hiding this comment

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

problem with this test: if the action doesn't throw any error, test still pass.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed: 435f086

Copy link
Contributor

@dpordomingo dpordomingo left a comment

Choose a reason for hiding this comment

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

Wow!!! it worked perfectly! Many many thanks!
I only detected some (imho) UX problems that should be confirmed by @ricardobaeta in a next iteration.

</p>
</Col>
<Col xs={5} className="text-right">
<FormGroup controlId="uploadTest">
Copy link
Contributor

Choose a reason for hiding this comment

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

If uploadDisabled is true because it is being created a new experiment, the upload button should not appear at all.

{Math.round(exp.progress)}%
</td>
<td className="experiments-list__additional-actions">
{/*
Copy link
Contributor

Choose a reason for hiding this comment

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

leftover?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's in master, I just changed the indentation 🤷‍♂️

return this.props
.experimentCreate(this.state.nameVal, this.state.descriptionVal)
.then(expId => {
this.props.onOpenEdit(expId);
Copy link
Contributor

Choose a reason for hiding this comment

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

It is kind of weird that after creating an experiment, the same modal is opened again without giving any special feedback to the user.

After creating a new experiment, I wouldn't open the modal automatically (as in edition-mode), so the user should open it manually -> she/he will be then aware of the edit-state.

this.props.editModalExperiment.id,
this.state.nameVal,
this.state.descriptionVal
);
Copy link
Contributor

Choose a reason for hiding this comment

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

The save button should not open the same modal again without clear feedback.

  • If the update succeeds, it should be said so, and the modal should be closed
  • if the update fails, it should be shown an error

<FormGroup controlId="uploadTest">
<FormControl
type="file"
onChange={this.handleUpload}
Copy link
Contributor

Choose a reason for hiding this comment

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

I would not push/save the new DB till the user press "save".

@dpordomingo
Copy link
Contributor

@carlosms you did a great job, many thanks.

Since most important requirements were already addressed, we can merge, so let's proceed as it follows:

  • you squash/rebase and whatever you think it should be done,
  • then you merge,
  • then I'll open an issue with mi UX concerns so @ricardobaeta will be able to address them,
  • and I'll open a new issue with the issue in one testcase as it was spotted by @smacker

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Add api call & reducer for sqlite DB upload

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Rename OnProgress -> InProgress

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Update experiments test snapshot

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Remove /experiments route, move btns to /dashboard

Adds tmp buttons to the dashbard to test the API

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

New modal component to create new experiments

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Add experiment update action to the frontend

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Add the upload DB option to the modal

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Add styling

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Refactor ExperimentEditModal.handleSave

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Refactor ExperimentEditModal state from props

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Refactor experiments modal state out of the store

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Rename ID -> Id for consistency

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Update tests

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Fix bug in tests

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Fix bug when create action fails

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>

Add expect.hasAssertions to tests

Signed-off-by: Carlos Martín <carlos.martin.sanchez@gmail.com>
@carlosms carlosms merged commit 11d7ad9 into src-d:master Mar 14, 2018
@carlosms carlosms deleted the issue-173 branch March 14, 2018 17:54
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.

4 participants