-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Add a utility to detect description changes #1127
Conversation
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 looks great. I've added some review notes to help you get Flow to pass and with suggestions to simplify the code and improve the usefulness of it's results.
} | ||
} | ||
|
||
if (oldType && !(newType instanceof oldType.constructor)) { |
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.
Flow cannot see this as a cross-type refinement, plus it's a bit challenging to understand. I would instead make instanceof invariants for the oldType
within each case below
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 did it this way because spelling out the instanceof
s actually becomes really messy in its own right to handle the cases where oldType
is null/undefined, and where oldType
is present but a different kind from newType
. I'll play with it a bit to see if I can make it nicer, but any suggestions would be very welcome.
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.
In this case, below line 50, you could just mirror the if statement above in an invariant while including the falsey check:
invariant(
!oldType ||
oldType instanceof GraphQLObjectType ||
oldType instanceof GraphQLInterfaceType ||
oldType instanceof GraphQLInputObjectType,
'Expected oldType to also have fields'
);
newType instanceof GraphQLInterfaceType || | ||
newType instanceof GraphQLInputObjectType | ||
) { | ||
const oldTypeFields: ?GraphQLFieldMap<*, *> = oldType |
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 this differentiate between the old type not existing vs the old type existing but the field not existing?
It could be overwhelming to see a ton of "Description added on new field" for every field on a type that was newly added, that might not be helpful.
Instead perhaps "New type added with description" and "New field added with description" should be different from "Description added on field"
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 could be overwhelming to see a ton of "Description added on new field" for every field on a type that was newly added, that might not be helpful.
The goal was to provide our docs team with a comprehensive set of all the new strings that needed reviewing; I'll type these differently when I add stronger types per your earlier comment, and then the caller can filter as desired.
if (newField.description) { | ||
if (!oldField) { | ||
descriptionChanges.push( | ||
`Description added on new field ${newType.name}.${newField.name}`, |
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 wording is a little confusing. How do you add to something that is new? Add implies additive to the existing state
export function findDescriptionChanges( | ||
oldSchema: GraphQLSchema, | ||
newSchema: GraphQLSchema, | ||
): Array<string> { |
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 would suggest mirroring the return value of the breaking change detection which offers what kind of detection was encountered. It looks like there are a handful of types here to differentiate: new types, new fields on existing types, added descriptions where there were none, descriptions changed, descriptions 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.
Also, if these are complex objects, you could contain references to the old/new schema elements themselves, that would allow you to use the output of this function to build a review tool. Currently you just have english text which requires manually looking up the described types to see what changes actually occurred, which would be limited in usefulness.
); | ||
} | ||
} | ||
}); |
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.
Much of the logic above looks repetitive, with the exception of the strings referencing each kind of type. Could you factor out the common logic?
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'll take a look. Javascript isn't my forte so for a first pass I definitely stuck with a simpler more explicit style.
const oldField = oldTypeFields ? oldTypeFields[fieldName] : null; | ||
const newField = newTypeFields[fieldName]; | ||
|
||
if (newField.description) { |
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 (newField && newField.description) {
bb202a5
to
4576e13
Compare
Sorry for the delay on this; finally got flow passing and I think I addressed all the code review comments. Please let me know if there's anything else. |
Thanks for the update and sorry for the review delay - it appears the test coverage is still below where we usually try to keep it. Here's a link from the coveralls report: https://coveralls.io/builds/14868261/source?filename=src%2Futilities%2FfindDescriptionChanges.js Could you add more tests to ensure all aspects of the new functionality are well tested? |
We use this internally for pinging docs team for PR reviews.
Strongly type the return objects, use invariants in a way to satisfy flow, and DRY up one common function.
4576e13
to
9dd09cd
Compare
Sorry for the delay again, I think we're both just busy :) I've added some tests, so Coveralls now says overall coverage has increased. There's only one line (86) that's not covered at all and I'm not sure why - is |
oldType instanceof GraphQLObjectType || | ||
oldType instanceof GraphQLInterfaceType || | ||
oldType instanceof GraphQLInputObjectType, | ||
'Expected oldType to also have fields', |
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 shouldn't be an error.
For example:
scalar Date
type Date {
day: Int
month: Int
year: Int
}
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 to satisfy Flow (#1127 (comment)).
This (and the enum case) are breaking schema changes, so an error seemed appropriate? Would you prefer they were silently ignored? If so, how do I do that in a way that Flow will recognize?
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.
So if I do breaking change in my schema I don't need to notify my documentation team about other non-breaking changes.
BTW Breaking changes is normal in many cases for example if you have your API in public beta.
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, you're right. I was thinking too much about our specific case (where breaking changes already ping a substantial set of people for review and the docs changes are less relevant) but in the general case this should absolutely work.
I'm still not sure how to satisfy Flow without an invariant, but that's purely me not being much of a javascript developer.
invariant( | ||
!oldType || oldType instanceof GraphQLEnumType, | ||
'Expected oldType to also have values', | ||
); |
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.
Same here:
scalar Month
enum Month {
Jan
Feb
# ...
}
FIELD: 'FIELD', | ||
TYPE: 'TYPE', | ||
ARGUMENT: 'ARGUMENT', | ||
ENUM_VALUE: 'ENUM_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.
Your missing directives + you need to check arguments of directives in findDescriptionChanges
}; | ||
|
||
export const DescriptionChangeType = { | ||
OBJECT_ADDED: 'OBJECT_ADDED', |
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 I understand it trigger when any new type is added not only GraphQLObjectType
. So I think it should have a different name.
@@ -0,0 +1,163 @@ | |||
/* eslint-disable no-restricted-syntax */ | |||
// @flow |
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.
Please use the same header license + @flow strict
as other files
@@ -0,0 +1,163 @@ | |||
/* eslint-disable no-restricted-syntax */ |
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.
Please remove and fix all eslint errors if any.
@eapache Sorry for not reviewing it earlier. My 2¢: Different developers and teams have different needs but we can't add From #981:
In that case changes in Moreover, some documentation teams would be interested in being notified of new types without description. I also see a use case for detecting any non-documentation (not in So I think instead of adding new
It will allow to implement your particular check as: const diff = diffSchema(oldSchema, newSchema);
for (const newType of diff.added.types) {
if (newType.description) {
console.log('Description of a new type ...');
}
newType.getFields().forEach(reportNewField);
}
for (const typeDiff of diff.changed.types) {
if (typeDiff.added.description) {
console.log('Added type description ...');
}
typeDiff.added.fields.forEach(reportNewField);
if (typeDiff.changed.description) {
console.log('Changed type description ...');
}
for (const fieldDiff of typeDiff.changed.field) {
if (fieldDiff.added.description) {
console.log('Added field description ...');
// ...
}
}
}
function reportNewField(field) {
if (field.description) {
// ...
}
} I think it would be more generic approach and allow other developers to easily implement their project-specific needs. @leebyron What do you think? |
You're not wrong, however a full The only thing I can think might still belong outside of
Also a good point. We just have CI fail in this case, but I imagine that isn't universal. |
Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need the corporate CLA signed. If you have received this in error or have any questions, please contact us at [email protected]. Thanks! |
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks! |
Closing since it's outdated but extracted |
We use this internally for pinging docs team for PR reviews.
Discussed briefly at #981 cc @leebyron.
Flow is complaining about some of this code, but Flow is just wrong; the equivalence check between
newType
andoldType
provides the necessary safety guarantees. Not sure what the preferred solution is here.