-
Notifications
You must be signed in to change notification settings - Fork 1
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
Create new Questionnaire (integration) #140
Conversation
88dc97f
to
f93b549
Compare
src/pages/CreateQuestionnaire.js
Outdated
onChange: PropTypes.func | ||
history: PropTypes.object.isRequired, // eslint-disable-line | ||
loading: PropTypes.bool.isRequired, | ||
onUpdate: PropTypes.func.isRequired, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need error
propType here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bennpearson currently not dealing with errors, probably need to at some point though: https://trello.com/c/oQ5Nn9ll/88-error-handling
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, couple of small points
src/components/Forms/Input/index.js
Outdated
type: PropTypes.oneOf(["text", "checkbox", "radio"]).isRequired, | ||
value: PropTypes.oneOfType([ | ||
defaultValue: PropTypes.oneOfType([ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why defaultValue
is you're now using onChange
handlers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we use value
then you're depending on a (debounced) roundtrip to the api to update the value
—makes for a rather laggy typing experience.
Using defaultValue
sets the value on initial render but then the state of the value is handled internally, with updates being periodically sent to the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't be saving as a user types. The value of the form shouldn't be tied to a request to the server. It should go into the store, then pulled out when necessary.
In any case, saving as a user types opens up corner cases like rollback happening as user types - would actually cause them to lose work!
I personally think we should do auto-saving as a separate story rather than ad-hoc, as that tends to result in a better solution (e.g. perhaps it can be done cleanly with middleware?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we address this as a separate "saving/auto-saving" story when we have some more UI to work with?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's wise. How about changing to autosave on blur for now? Conceptually simpler and less corner cases
src/containers/Breadcrumb.js
Outdated
const route = getRouteByPath(pathname); | ||
const getQuestionnaire = gql` | ||
query GetQuestionnaire { | ||
questionnaire(id: 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be paramterised
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I paramterised the gql on Publisher, see here for an example.
src/containers/Breadcrumb.js
Outdated
const route = getRouteByPath(pathname); | ||
const getQuestionnaire = gql` | ||
query GetQuestionnaire { | ||
questionnaire(id: 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the breadcrumb requesting this data? Seems like an odd place for the request to be made. Especially when CreateQuestionnaire seems to do the same. Seems like the type of thing that should flow down through props
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't entirely happy with this but the alternative is a significant refactor. Structurally, CreateQuestionnaire
and the Breadcrumbs
are in different timezones, the data fetching would have to be done at layout level and then the props passed down through several layers of components, which comes with its own problems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth revisiting the architecture at some point. Particularly at which "level" we use container components. The Layout/Page hierarchy was something I threw together to get it working initially. If it is causing problems then we can revisit it. But it's probably outside the scope of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The value goes into the store, presumably? So couldn't Breadcrumb be connected to store and pull the value out of there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@samiwel I think we should refactor if it becomes an issue. At the moment it's not causing any problems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm yeah. But that was reading directly from the store and being dependent on the keying strategy used. Though there are methods for pulling stuff from the store without hitting the server: http://dev.apollodata.com/core/read-and-write.html. readQuery
or readFragment
are what we want, I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wasn't aware of that, I'll look into it. However, does't that rely on the request having been made elsewhere? Eg. if you navigate straight to the /design
page there's no request currently so the breadcrumbs won't have a clue what's going on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it would rely on that. Haha, guess this goes back to the id
in the url issue. OK let's leave for now. Do you want to add some tasks to trello to cover all the stuff we're going to defer for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking ahead—the Header/Nav will probably need to be more connected. I think that will present opportunities to connect higher up and pass things like title down to Breadcrumbs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds sensible. I wouldn't be surprised if it eventually ends up connected at page level, with everything flowing down from there
navigation, | ||
legalBasis, | ||
theme, | ||
__typename |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you need this in your query. Is there a reason for it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was added whilst attempting to get MockedProvider
to work in the tests. See: apollographql/react-apollo#674 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did it make it work? If not, can it be removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ignore, I see you removed it
`; | ||
|
||
const mapResultsToProps = results => { | ||
const { data: { loading, questionnaire } } = results; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you're going to do deep destructuring, what do you think about formatting it so it mirrors the structure of the object? e.g.
const {
data : {
loading,
questionnaire
}
} = results;
alternatively:
const { loading, questionnaire } = results.data;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer the latter 👍
|
||
const mapResultsToProps = results => { | ||
const { data: { loading, questionnaire } } = results; | ||
// console.log(results.data.error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove pls :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops
); | ||
export const withData = graphql(getQuestionnaire, { | ||
props: mapResultsToProps, | ||
options: { variables: { id: 1 } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't have the id
hard-coded
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where should this live? We have to hardcode it somewhere, I think as we're only loading one questionnaire.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you create a function called withData which accepts a parameter (the Id) and returns the graphql? e.g. something like:
const withData = questionnaireId => {
return graphql(getQuestionnaire, {
props: mapResultsToProps,
options: { variables: { id: questionnaireId } }
});
}
Then call with withData(1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The id should come from the url i guess? When the questionnaire obj is in memory, you should have its id
available
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@samiwel This just moves the hardcoded value elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@WickyNilliams That would work but in the interests of moving PRs on a bit quicker, I think it's better that we solve problems (such as being able to dynamically select a questionnaire) when we need that feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The URL stuff we could do later as that's a bigger task. But for now, you could pass the id back from the component?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's still just hardcoding the value elsewhere, solving a problem we don't have yet. We likely won't be passing that id in from the component in future so it will require another refactor further down the line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean it should receive as prop and pass back? Or the callback could grab the value from somewhere before calling mutate. Either way, I absolutely think we be shouldn't be hardcoding values now
src/pages/CreateQuestionnaire.js
Outdated
export class CreateQuestionnairePage extends Component { | ||
constructor(props) { | ||
super(props); | ||
this.debouncedChangeHandler = debounce(this.onChange.bind(this), 300, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
surprised this needs binding if you're using a property initializer below, since that should already be bound to this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was also surprised but it doesn't work otherwise, I think related to this answer on SO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, that answer suggested this should work:
onChange = debounce(value => {
this.setState(value, () => {
this.props.onUpdate(this.state);
});
}, 300, { leading: true });
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@WickyNilliams that was the first thing I did, it doesn't work. :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, screw it then. Leave as-is for now!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving to update on blur
just made this irrelevant anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"La-zer"
src/pages/CreateQuestionnaire.js
Outdated
</ActionButtonGroup> | ||
{loading | ||
? <div>Loading...</div> | ||
: <Form handleSubmit={this.onSubmit}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you extract the two branches into methods? Much clearer to read loading ? this.renderLoadingIndictator() : this.renderForm()
than a huge block of JSX
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This just moves the huge chunk of JSX problem elsewhere, I've done it anyway though :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha fair point :) But a huge chunk of JSX isn't a problem in itself, it's just not great hanging off a ternary operator.
src/reducers/index.js
Outdated
const createReducer = reducers => | ||
combineReducers({ | ||
questionnaire, | ||
router: routerReducer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could always put { routerReducer as router }
in your import statement if you want to consistently use shorthand properties
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/apollo/client.js
Outdated
return null; | ||
}, | ||
networkInterface: createNetworkInterface({ | ||
uri: "http://localhost:4000/graphiql" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be an environment variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It bloody should be 🎉
@@ -1 +1,2 @@ | |||
REACT_APP_BASE_NAME="/eq-author" | |||
REACT_APP_API_URL="" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we able to override the REACT_APP_API_URL
value with an environment variable. Or do we need to default it to process.env.REACT_APP_API_URL
here?
We need to be able to customise this for successful deployment to CloudFoundry.
15e01fa
to
bc59cf7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is so much simpler than we had before! Definitely moving in the right direction 👍
Few little points i think are worth fixing
@@ -87,11 +88,13 @@ | |||
"dependencies": { | |||
"connected-react-router": "^4.2.1", | |||
"enzyme": "^2.8.2", | |||
"global": "^4.3.2", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An accident!
src/apollo/client.js
Outdated
import { ApolloClient, createNetworkInterface } from "react-apollo"; | ||
|
||
const client = new ApolloClient({ | ||
ddTypename: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/ddTypename/addTypename
src/components/BaseLayout/index.js
Outdated
hasUtilityBtns: false | ||
}; | ||
|
||
BaseLayout.displayName = "BaseLayout"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the need for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A workaround for this bug which breaks snaphot tests on Travis.
src/components/Breadcrumb/index.js
Outdated
return ( | ||
<BreadcrumbNav aria-label="breadcrumb"> | ||
<BreadcrumbList> | ||
<BreadcrumbItem key="/"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't need key
prop, unless elements are in an array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True
src/components/Breadcrumb/index.js
Outdated
}) | ||
).isRequired | ||
<BreadcrumbItem key={breadcrumb.path}> | ||
<BreadcrumbLink to={breadcrumb.path}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should have the aria-current
attribute on the current breadcrumb
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Linter complains about using aria-current
, apparently it's not in the spec
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm that's odd. Appears to be part of the standard here https://www.w3.org/TR/wai-aria-1.1/#aria-current. Perhaps we should raise a bug against the eslint plugin?
id: PropTypes.string, | ||
handleChange: PropTypes.func, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onChange should be the name of this kind of prop (here and anywhere else), to align with regular DOM components. handleChange should only be used internally e.g.
class ExampleComponents extends Component {
handleChange = (e) {
const value = e.target.value;
this.props.onChange(value);
};
render() {
return (
<input
type="text"
value={this.props.value}
onChange={this.handleChange}
/>
);
}
}
return ( | ||
<Center> | ||
<Panel> | ||
<Form handleSubmit={handleSubmit}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as before, this should be onSubmit. onX for external props, handleX for internal method
this.setState(questionnaire); | ||
} | ||
|
||
onChange = value => this.setState(value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as discussed, these should be called handleChange, handleSubmit etc
this.props.onUpdate(this.state); | ||
}; | ||
|
||
onSubmit = e => e.preventDefault(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handleX
|
||
export default compose(connect(mapStateToProps), withData, withMutation)( | ||
QuestionnaireMeta | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a lot of duplicated code between this and src/containers/QuestionnaireDesignPage/index.js
. Should extract common queries etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think the best way to do so is to implement the webpack loader plugin, can this be done as a separate task?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added task to trello: https://trello.com/c/pBSrctE2/99-investigate-use-of-webpack-loader-for-graphql-files
@@ -3,11 +3,11 @@ import { shallow } from "enzyme"; | |||
import { Form } from "components/Forms/Form"; | |||
|
|||
let wrapper; | |||
const handleSubmit = jest.fn(); | |||
const onSubmit = jest.fn(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These functions should actually be called handleSubmit haha. But don't worry if they're just tests!
src/components/RichTextArea/index.js
Outdated
@@ -52,7 +52,7 @@ export default class RichTextArea extends Component { | |||
}; | |||
} | |||
|
|||
handleChange = value => { | |||
onChange = value => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be handleChange
Changes
Added clientside logic for connecting to Author API:
meta
from ReduxTesting