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

Support tracked and other improvements #1035

Merged
merged 12 commits into from
Oct 23, 2019

Conversation

patricklx
Copy link
Collaborator

@patricklx patricklx commented Sep 22, 2019

  • Dont use observe for update properties
  • Add icons for tracked, function, getter
  • support computed that return undefined
  • sort and group properties by type.
    Service -> Tracked -> Computed -> Getter -> Property -> Function
  • support complex getters, can always be calculcated when needed
  • include functions, (why not?)

@RobbieTheWagner
Copy link
Member

Hi @patricklx, thanks so much for the PR! Supporting tracked would be 💯

@RobbieTheWagner
Copy link
Member

@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!

@patricklx patricklx force-pushed the support-refresh.same branch 2 times, most recently from 03ccee8 to dfa8764 Compare September 23, 2019 15:51
@patricklx patricklx changed the title [wip] Support tracked and other improvements Support tracked and other improvements Sep 23, 2019
@nummi
Copy link
Collaborator

nummi commented Sep 23, 2019

@patricklx, is this still a work in progress? When I try to run this locally I get this error: Uncaught TypeError: desc.hasOwnProperty is not a function

@patricklx
Copy link
Collaborator Author

@nummi mmm, its working fine for me...
when does it happen? Do you select a specific object, component, service, mixin... ?

I have a project where I still have both types, EmberObject.extend or es6 class.
It's working everywhere for routes, controller, components and services...

@patricklx
Copy link
Collaborator Author

Working on fixing the tests...

@nummi
Copy link
Collaborator

nummi commented Sep 24, 2019

EDIT: FIXED 🎉


https://deprecations.emberjs.com

image

VM77:3331 Uncaught TypeError: desc.hasOwnProperty is not a function

@patricklx patricklx changed the title Support tracked and other improvements [wip] Support tracked and other improvements Sep 24, 2019
@patricklx patricklx changed the title [wip] Support tracked and other improvements Support tracked and other improvements Sep 25, 2019
@patricklx
Copy link
Collaborator Author

@rwwagner90 @nummi this should be quite ready now. Many changes...
I hope you agree with them :)

@patricklx patricklx force-pushed the support-refresh.same branch 2 times, most recently from 2892119 to 7fafb04 Compare September 25, 2019 16:17
background-color: var(--base08);

&:before {
content: "F";
Copy link
Collaborator

Choose a reason for hiding this comment

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

How about "ƒ" ? :)

@RobbieTheWagner
Copy link
Member

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 😃

@RobbieTheWagner
Copy link
Member

@patricklx please let us know when this is done and ready for review!

@patricklx
Copy link
Collaborator Author

@rwwagner90 it's ready now :)

@RobbieTheWagner
Copy link
Member

@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?

@@ -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) {
Copy link
Collaborator

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?

Copy link
Collaborator Author

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 ...)

Copy link
Collaborator

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?

Copy link
Member

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.

Copy link
Collaborator Author

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

Copy link
Collaborator Author

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

Copy link
Member

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.

Copy link
Member

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.

Copy link
Collaborator Author

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...

Copy link
Member

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.

@RobbieTheWagner
Copy link
Member

@pzuraq wanted to get your opinions on these changes, as they are around tracked and I don't know much about that yet.

@RobbieTheWagner
Copy link
Member

@patricklx have you been testing this against an app? If so, is it something we could also use for testing?

@patricklx
Copy link
Collaborator Author

patricklx commented Oct 3, 2019

Hi, @rwwagner90
Yes, I've been testing with an app.
Got the dev build up:
http://116.203.88.77:9020
Login: test, test

@@ -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) {
Copy link
Member

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?

Copy link
Collaborator Author

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

Copy link
Member

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?

Copy link
Collaborator Author

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.

Copy link
Member

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.

Copy link
Contributor

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:

  1. Walk up the prototype chain to see if we have a toString() at all (that isn't the standard Object.toString()) or
  2. 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)

Copy link
Collaborator Author

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

Copy link
Contributor

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 😕

@@ -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)) {
Copy link
Member

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?

Copy link
Collaborator Author

@patricklx patricklx Oct 18, 2019

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

Copy link
Member

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?

Copy link
Contributor

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:

  1. Is an accessor, e.g. has get in its descriptor
  2. Has a tagForProperty
  3. 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',
Copy link
Contributor

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) {
Copy link
Contributor

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)

@@ -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) {
Copy link
Contributor

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:

  1. Walk up the prototype chain to see if we have a toString() at all (that isn't the standard Object.toString()) or
  2. 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) {
Copy link
Contributor

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.

@@ -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)) {
Copy link
Contributor

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:

  1. Is an accessor, e.g. has get in its descriptor
  2. Has a tagForProperty
  3. Is not a computed or service

Then it is a tracked property.

objectDebugInfo = null;
}
if (object.content) {
object = object.content;
Copy link
Contributor

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)

Copy link
Collaborator Author

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?

Copy link
Contributor

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.

Copy link
Collaborator Author

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

}

if (!options.isService) {
options.isService = desc._getter && desc._getter.name === 'getInjection';
Copy link
Contributor

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.

});

mixins.mixins.push(mix);
mixin.properties = propertiesForMixin({ mixins: [mixin] });
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
mixin.properties = propertiesForMixin({ mixins: [mixin] });
// Clean the properties, removing private props and bindings, etc
mixin.properties = addProperties(mixin, {});

}
mixinDetails.push(...objectMixins.slice(0, index));
const objectName = mixinDetails[0].name;

mixins.forEach(mixin => {
Copy link
Contributor

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:

  1. Walk the entire prototype tree, gathering all information about the object/classes and putting it onto some fake-mixins
  2. 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

Copy link
Collaborator Author

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));
Copy link
Contributor

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

@pzuraq
Copy link
Contributor

pzuraq commented Oct 22, 2019

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 😄

@pzuraq
Copy link
Contributor

pzuraq commented Oct 23, 2019

@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 🎉

@patricklx
Copy link
Collaborator Author

patricklx commented Oct 23, 2019

mmm, okay.
But now I only see some strange properties in the Own Properties, which might be correct, but not useful, since those are just set my ember itself, e.g. renderer and parentView etc...

Now, I always can close the Own Properties tab and open some weird named tab :) .
This worked with Ember.Object.extend, since it would set CPs and properties on the instance itself, but its not happening with es6 classes anymore, so Own Properties is not so useful for debugging/development. Can we have a Useful Info tab then.... where we merge Own Properties and the one from the instance class prototype?
In a previous merged PR I had the class name for classes for components/routes in a tab and made it expanded by default. Lets revert to that then?

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 export default class extends,
it shows me <_class2:ember123>

in the top of the object inspector it shows the mixin name (from toString) if there is one...

I cannot dig into any belongsTo in the models...
and I cannot get the content for PromiseManyArray, it just gets me the list of internal models when I click on .content. Both were fixed by including the proxy content. I would rather skip over the proxies, unless there is a special property set on them. e.g. _showProxyDetails
But I open another PR for that.

@patricklx
Copy link
Collaborator Author

patricklx commented Oct 23, 2019

@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

@RobbieTheWagner
Copy link
Member

@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?

@patricklx
Copy link
Collaborator Author

patricklx commented Oct 23, 2019

@rwwagner90 before it was showing own properties and the first class prototype separately. It was also expanding both.
I wanted to merge those, since those 2 have all the useful information. Now its back to 2 separated and expanded

@RobbieTheWagner
Copy link
Member

@patricklx we should keep it how it was before 👍

@RobbieTheWagner
Copy link
Member

@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.

@pzuraq
Copy link
Contributor

pzuraq commented Oct 23, 2019

I cannot dig into any belongsTo in the models...

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 😄

And now the names of the my components are always <@ember/component:ember123> instead of showing the Classname

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 toString() behavior upstream. I also didn't want to remove the GUID stuff, in case some users were using it. However, it would be very nice to get some progressive enhancement here just by switching to native classes. Let me think about it for a minute..

Can we have a Useful Info tab then.... where we merge Own Properties and the one from the instance class prototype?

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

@pzuraq
Copy link
Contributor

pzuraq commented Oct 23, 2019

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.

@RobbieTheWagner RobbieTheWagner merged commit e3ee13a into emberjs:master Oct 23, 2019
@nummi
Copy link
Collaborator

nummi commented Oct 23, 2019

Thank you @patricklx 🎉

@RobbieTheWagner
Copy link
Member

@patricklx we definitely owe you a 🍺for your help! Thanks so much!

@patricklx
Copy link
Collaborator Author

👍, thanks. And sorry that this PR got so big... 😅

@patricklx patricklx deleted the support-refresh.same branch October 24, 2019 08:37
patricklx added a commit to patricklx/ember-inspector that referenced this pull request Sep 19, 2022
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants