-
Notifications
You must be signed in to change notification settings - Fork 286
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
Support tracked and other improvements #1035
Support tracked and other improvements #1035
Conversation
Hi @patricklx, thanks so much for the PR! Supporting tracked would be 💯 |
@patricklx I apologize, but I just merged a PR that moved around some things, so there are some conflicts you will need to resolve. Sorry about that! |
03ccee8
to
dfa8764
Compare
@patricklx, is this still a work in progress? When I try to run this locally I get this error: |
@nummi mmm, its working fine for me... I have a project where I still have both types, EmberObject.extend or es6 class. |
Working on fixing the tests... |
705b8cb
to
60c7268
Compare
EDIT: FIXED 🎉https://deprecations.emberjs.com
|
985add0
to
8c98ed4
Compare
@rwwagner90 @nummi this should be quite ready now. Many changes... |
2892119
to
7fafb04
Compare
app/styles/object_inspector.scss
Outdated
background-color: var(--base08); | ||
|
||
&:before { | ||
content: "F"; |
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.
How about "ƒ" ? :)
Thanks so much for this PR @patricklx! This is super amazing and we really appreciate your help! We will review this in the coming days and get it in ASAP 😃 |
@patricklx please let us know when this is done and ready for review! |
@rwwagner90 it's ready now :) |
@patricklx it looks like we have a couple test failures. I know you already fixed a lot of failures, but do you mind taking a look? |
app/controllers/application.js
Outdated
@@ -78,6 +78,9 @@ export default Controller.extend({ | |||
* Called when inspecting an object from outside of the ObjectInspector | |||
*/ | |||
activateMixinDetails(name, objectId, details, errors) { | |||
if (this.mixinStack.objectAt(0) && this.mixinStack.objectAt(0).objectId === objectId) { |
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 is this check preventing?
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 there are many renders, it will remove and add the same object in the inspector everytime, therefore making it impossible to go into properties and it also decreases the performance of the inspector. (If by some mistake you have 100ths of renders / s ...)
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 end up adding another commit to this PR would you mind putting in a 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.
Would it perhaps be a good idea to wrap in some sort of throttle or debounce? That way if we render hundreds of times, we could make it only actually render a few of those times.
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.
actually, I remember the real issue now.
When a rerender happens, and i'm currently digging into an object, it will reset the object inspector. Throwing away my current inspection.
I will update the 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.
I had many, yes. fixes it, but still once every few seconds, and it will reset the object inspector...
its only gets outdated if new properties are added after the object was created. But if you switch between objects in the inspector it will refresh
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 is your app doing that is making it constantly refresh? I think that sounds like an issue with the app, not inspector.
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 want to not refresh, we should check for deep equality of the properties in the object you are inspecting, if nothing changes, there is no need to refresh, but if it does we need to refresh or you are looking at the wrong info.
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 is an issue with the app, but i think we should be able to use ember-inspector during app development with app issues :)
but I just tested without this change, and its working fine 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.
I think we either should remove this check entirely or it should be a check that basically nothing on the instance changed. If all the properties remain the same, skip refreshing, etc. Might be easier to just remove for now.
@pzuraq wanted to get your opinions on these changes, as they are around |
@patricklx have you been testing this against an app? If so, is it something we could also use for testing? |
Hi, @rwwagner90 |
seems it will take some time to review the tracked part? otherwise, here are some hints for tracked if you want to review yourself: also note that tagForProperty will setup mandatory setters, which we want to prevent while using ember-inspector |
ember_debug/object-inspector.js
Outdated
@@ -500,7 +500,7 @@ export default EmberObject.extend(PortMixin, { | |||
if (proto.hasOwnProperty('toString') && !proto.hasOwnProperty('hasOwnProperty')) { | |||
return proto.toString(); | |||
} | |||
if (name === 'Class' || name.startsWith('_')) { | |||
if (name === 'Class' || name.startsWith('_') || name.length === 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.
What is the use case for name.length === 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.
in prod build the class names are uglyfied to 1 character
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.
@patricklx wouldn't that not always be the case though? What if you have more classes than the 26 letters in the alphabet? Would it make it 2 characters? @pzuraq @rwjblue is there a more reliable check we can do 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.
mmm, right, it could happen. but I think it must be more than 26 classes in a module or in same scope?
But I guess we could set it to length <= 3
? should be 17,576 different classes.
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 like to hear from @pzuraq, @chancancode, or @rwjblue on if there is something less brittle we could do 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.
I think it's more related to variables in scope, classes tend to be the top level variables of a module, but you could definitely have others, maybe more than 26. In any case, I do think this is pretty brittle.
My question is, what is this case handling that the others don't? I believe proto.toString()
should cover anything resolved by the container, and class.constructor.name
should cover most other things. I suppose if there's another toString()
that is defined somewhere in the prototype chain that could be valuable - either someone manually put one there, or it's one of the default <unknown:mixin>
type ones. In that case, maybe we can either:
- Walk up the prototype chain to see if we have a
toString()
at all (that isn't the standardObject.toString()
) or - Just go with the default class name. Even minified, it may be a bit more useful than a generic
<unknown:mixin>
(though it'd be less useful than a custom one, probably)
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.
but if we only use prototype lookup, it would show to the toString
from mixins...
And only default class name will not show the toString
for mixins
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.
From what I can see, toString
from mixins should be applied to the class, and shouldn't belong to the mixin directly. I actually can't find a way to name mixins at all, which is a bit unfortunate, but that seems to be by design 😕
ember_debug/object-inspector.js
Outdated
@@ -842,6 +845,12 @@ function calculateCPs(object, mixinDetails, errorsForObject, expensiveProperties | |||
tagInfo.tag = metal.track(() => { | |||
value = calculateCP(object, item.name, errorsForObject); | |||
}); | |||
if (!tagInfo.tag.subtag && !tagInfo.tag.subtags && tagInfo.tag === metal.tagForProperty(object, item.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.
I'm not sure what these ifs are for, but it seems like maybe it should be a util function to check for whatever this is checking?
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 to detect tracked
in prod builds.
if only a tracked is in a tracking context metal.track(...)
, then the resulting tag will be the same as the metal.tagForProperty
and will not have any subtag or subtags.
I think emberjs should provide a way to check if a property on an object (or descriptor) is a tracked. Then we can remove this in the 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.
@chancancode @pzuraq thoughts on 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 actually think this should be the only way we do this, the above code that detects CURRENT_TRACKER
is much more brittle, it could break at any time. This is more solid, it likely won't break as long as we have Ember.meta
.
We should get rid of the check for subtag
and subtags
and instead move the next if
checks up, if the value has:
- Is an accessor, e.g. has
get
in its descriptor - Has a
tagForProperty
- Is not a computed or service
Then it is a tracked property.
@@ -21,7 +46,15 @@ export default Component.extend({ | |||
* @property sortProperties | |||
* @type {Array<String>} | |||
*/ | |||
this.sortProperties = ['name']; | |||
this.sortProperties = [ | |||
'isFunction', |
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 special casing arrays would be fine, this could also be done in a followup though. I agree that properties and tracked fields should likely be grouped together, the eslint rule seems to make the most sense. I'd say let's do that to get this merged, then followup to improve the story for arrays specifically.
|
||
function getTagTrackedProps(tag, ownTag, level=0) { | ||
const props = []; | ||
if (!tag || level > 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.
We should add a comment here, if I'm thinking this through right the logic is because you don't want to track indirect dependencies (e.g. dependencies of CPs that get tracked, etc)
ember_debug/object-inspector.js
Outdated
@@ -500,7 +500,7 @@ export default EmberObject.extend(PortMixin, { | |||
if (proto.hasOwnProperty('toString') && !proto.hasOwnProperty('hasOwnProperty')) { | |||
return proto.toString(); | |||
} | |||
if (name === 'Class' || name.startsWith('_')) { | |||
if (name === 'Class' || name.startsWith('_') || name.length === 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.
I think it's more related to variables in scope, classes tend to be the top level variables of a module, but you could definitely have others, maybe more than 26. In any case, I do think this is pretty brittle.
My question is, what is this case handling that the others don't? I believe proto.toString()
should cover anything resolved by the container, and class.constructor.name
should cover most other things. I suppose if there's another toString()
that is defined somewhere in the prototype chain that could be valuable - either someone manually put one there, or it's one of the default <unknown:mixin>
type ones. In that case, maybe we can either:
- Walk up the prototype chain to see if we have a
toString()
at all (that isn't the standardObject.toString()
) or - Just go with the default class name. Even minified, it may be a bit more useful than a generic
<unknown:mixin>
(though it'd be less useful than a custom one, probably)
* @param value | ||
* @returns {string} | ||
*/ | ||
function typeOf(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 does seem like a bug in Ember, but it's probably fine for now to fix it here and try to upstream the fix later.
ember_debug/object-inspector.js
Outdated
@@ -842,6 +845,12 @@ function calculateCPs(object, mixinDetails, errorsForObject, expensiveProperties | |||
tagInfo.tag = metal.track(() => { | |||
value = calculateCP(object, item.name, errorsForObject); | |||
}); | |||
if (!tagInfo.tag.subtag && !tagInfo.tag.subtags && tagInfo.tag === metal.tagForProperty(object, item.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.
I actually think this should be the only way we do this, the above code that detects CURRENT_TRACKER
is much more brittle, it could break at any time. This is more solid, it likely won't break as long as we have Ember.meta
.
We should get rid of the check for subtag
and subtags
and instead move the next if
checks up, if the value has:
- Is an accessor, e.g. has
get
in its descriptor - Has a
tagForProperty
- Is not a computed or service
Then it is a tracked property.
ember_debug/object-inspector.js
Outdated
objectDebugInfo = null; | ||
} | ||
if (object.content) { | ||
object = object.content; |
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 definitely think skipping over proxies is a bad idea. I know that 9 times out of 10, they're a pretty transparent wrapper, but they can have their own state and such (not a great design pattern, but it is what it is)
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.
mmm, but for ember data it had issues with displaying data.
the _debugInfo
is accessed with get
, which passes through ember proxy.
It's also a bit annoying to click 2 times whenever I want to inspect e.g. belongsTo
data...
Can we make a special case for ember data models?
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, loaded up an Ember app to take a look, emberclear
, and it looks like currently at least models are already special cased. Whenever I click on a model, and then on a relationship within that model, it shows the model itself, not the proxy first. I think keeping that behavior would be fine since it's already established.
FWIW, I don't necessarily think this would be a bad idea either, but it seems out of scope for this PR. I think we should fix the existing behavior for sure, but any additional proxy changes should be followups.
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.
strange, for me it keeps failing in ember-data _debugInfo
... version 3.11.4
, but sure, i can extract this parts into another PR
ember_debug/object-inspector.js
Outdated
} | ||
|
||
if (!options.isService) { | ||
options.isService = desc._getter && desc._getter.name === 'getInjection'; |
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 seems pretty brittle. I think I'd rather stick with the previous two checks, and drop this one for now.
ember_debug/object-inspector.js
Outdated
}); | ||
|
||
mixins.mixins.push(mix); | ||
mixin.properties = propertiesForMixin({ mixins: [mixin] }); |
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.
mixin.properties = propertiesForMixin({ mixins: [mixin] }); | |
// Clean the properties, removing private props and bindings, etc | |
mixin.properties = addProperties(mixin, {}); |
ember_debug/object-inspector.js
Outdated
} | ||
mixinDetails.push(...objectMixins.slice(0, index)); | ||
const objectName = mixinDetails[0].name; | ||
|
||
mixins.forEach(mixin => { |
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, the new strategy appears to be:
- Walk the entire prototype tree, gathering all information about the object/classes and putting it onto some fake-mixins
- Put these on top, and then add mixins, and then add
EmberObject
at the very bottom
I think this is a bit problematic, because then the mixins are going to be out of order with the class prototypes they actually belong to. They'll also consist entirely of properties/descriptors that already show up on one of the prototype-mixins (unless they're overridden, which I guess technically they would all be?), e.g.:
class Foo extends Bar.extend(MyMixin) {}
class Bar extends Baz {}
class Baz extends EmberObject {}
// inspector shows
- Foo
- Bar
- Baz
- MyMixin
- EmberObject
I think ideally what we should do here is show only the classes themselves by default, and then have a sort of subview where you can examine the mixins of a particular class:
// inspector shows
- Foo
- Bar
- mixins: MyMixin
- Baz
- EmberObject
I think we could add the subview in a followup, but I'm not sure if we can show the out of order view in the meantime, that seems like it'd be really confusing. Maybe we could try to setup the order the correct way at least?
// inspector shows
- Foo
- Bar
- MyMixin
- Baz
- EmberObject
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 changed to logic for this part and added a test to check the correct order of prototypes and mixins
@@ -296,12 +301,14 @@ module('Ember Debug - Object Inspector', function(hooks) { | |||
|
|||
inspected.set('name', 'Alex'); | |||
|
|||
await new Promise(res => setTimeout(res, 400)); |
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.
At the least abstract this to be a separate helper I think
Overall this is looking pretty good, there are just a couple things that need to change I think to be consistent. I should be able to work on this tomorrow morning (I think I have push access to the branch as a maintainer), if that works, would definitely like to get this merged asap 😄 |
@patricklx Made a few changes to clean some things up. It's really important that we show an accurate representation of the object's inheritance, so really everything in the prototype chain should accurately only show its own properties. We're definitely lacking a couple of the APIs that would make this ideal, but I found a couple of ways to hack around it for the time being. I think with these changes this should be ready to merge, assuming tests pass 🎉 |
mmm, okay. Now, I always can close the And now the names of the my components are always <@ember/component:ember123> instead of showing the Classname, and if I didn't set a class name with in the top of the object inspector it shows the mixin name (from toString) if there is one... I cannot dig into any |
@pzuraq I added one more commit to show the first class protoype be default, since it has all the properties. This is how it was before this PR |
@pzuraq @patricklx we should try to preserve how the object inspector worked before. If it merged own properties and prototype properties, perhaps we should leave that? |
@rwwagner90 before it was showing own properties and the first class prototype separately. It was also expanding both. |
@patricklx we should keep it how it was before 👍 |
@patricklx I know you've already put a lot of work in on this, but for some of the things you mentioned like the class names being different, not being able to expand belongsTo, proxies, etc it would be 💯 if we added some tests for some of these things, so we can ensure it keeps working. |
This is not ideal, but this does appear to match the existing behavior. If I visit https://www.emberobserver.com/addons/ember-cli-babel and open the inspector, both before and after the change, relationships within the data store seem to work the same. We should definitely fix this, I just think it needs to be in a separate followup, with a lot of tests to make sure it doesn't get into this state again 😄
Hmm, I was thinking when I rewrote it that this would be solved by us removing classic components and EmberObject as a whole, or updating the
FWIW, I do think this would be useful. Like, normal JS objects in the console inspector do show all inherited values, but slightly greyed out. I think we can do something better here, but that's a somewhat orthogonal change to the main issues here, which are tracked support and getters, and better support for native classes. It's generally a good idea to try to break large changes like these up into smaller PRs, so we can review them more thoroughly. I actually think there will still need to be quite a few refactors (as I noted in the comment I left) so there's lots of opportunity to get a better DX |
Ok, updated it to be a bit more conservative, and to keep the GUID if it exists for the time being. In the long run I think we should be getting rid of that too, but for now I think this is a better intermediate step. Thanks again @patricklx for all the work in this PR! Seriously, it's been incredibly helpful 😄 I definitely want to make sure we solve the issues you brought up around proxies as well, especially the ones surrounding Ember Data, it does seem like the object explorer is pretty limited around it at the moment. We have a lot more work to do here, so I think we can roll out incremental improvements as time goes on. |
Thank you @patricklx 🎉 |
@patricklx we definitely owe you a 🍺for your help! Thanks so much! |
👍, thanks. And sorry that this PR got so big... 😅 |
* Support tracked and other improvements Dont use observe for update properties Add icons for tracked, function, getter support computed that return undefined group properties by type * add workaround for [object AsyncFunction] typeof just returns 'function' * improve production build detection of tracked and class names * fix * extract into helper * revert to previous inheritance/merging semantics * improve class name and inspector view * use classname only with ember toString * add more tests for new behavior * getter compat
Service -> Tracked -> Computed -> Getter -> Property -> Function