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

mutations in the tree view #504

Merged
merged 31 commits into from
Dec 8, 2020
Merged

mutations in the tree view #504

merged 31 commits into from
Dec 8, 2020

Conversation

oliver-sanders
Copy link
Member

@oliver-sanders oliver-sanders commented Sep 24, 2020

Addresses #339

Add mutations into the tree view.

Caveats:

  1. I've only added support for those mutations that don't require any information that we can't get from the context of the object we are mutating (e.g. trigger).
  2. At the moment mutations are only available on tasks/families.
  3. Support for the remaining mutations and the ability to provide optional arguments to come in a future PR. Until that time I've not removed the old mutations view, though I expect it is thoroughly broken.

This approach can be easily incorporated into other views.

TODO (this PR):

  • Tidy.
  • Test libs.
  • Test components? (have tested the menu opens, would be good to test mutation generation but can do later)
  • Fix commented out tests.

Requirements check-list

  • I have read CONTRIBUTING.md and added my name as a Code Contributor.
  • Contains logically grouped changes (else tidy your branch by rebase).
  • Does not contain off-topic changes (use other PRs for other changes).
  • Appropriate tests are included (unit and/or functional).
  • Appropriate change log entry included.
  • No documentation update required.

@oliver-sanders oliver-sanders added this to the 0.3 milestone Sep 24, 2020
@oliver-sanders oliver-sanders self-assigned this Sep 24, 2020
Comment on lines 35 to 41
<CylcObject :id="node.node.id">
<task
:status="node.node.state"
:isHeld="node.node.isHeld"
:progress=0
/>
</CylcObject>
Copy link
Member Author

@oliver-sanders oliver-sanders Sep 24, 2020

Choose a reason for hiding this comment

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

Basic premise:

If you've got something you wan't to be interactive, wrap it with a <CylcObject> and provide it the Cylc ID for the "thing" your element represents.

This wrapper handles context menus, pop-ups and mutations.

I did think about adding this functionality into the task/job components themselves which would make developing new views a little easier, however, this allows groups of components to be wrapped up together.

I'm not 100% happy with this approach. For example, what if you wanted to wrap an entire row in the tree view? You could do it, however, CylcObject and the Tree components would be fighting for interaction events.

Open to ideas.

Copy link
Member Author

@oliver-sanders oliver-sanders Oct 2, 2020

Choose a reason for hiding this comment

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

Also this really won't work with multiple selection etc. I think for that to work this functionality would have to be much more centralised. Perhaps this is the stepping stone.

Copy link
Member Author

Choose a reason for hiding this comment

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

Now thinking that this should be available somehow as a function something along the lines of:

interact([ids], [posX, posY])

This would, for example, allow the Tree view to handle multiple-selection then call the interact function with the IDs of the selected elements and the x,y position of the cursor to create the tooltip at?

In Vue terms this could be:

  • A directive, however, I don't know whether a directive can create the context-menu component.
  • A mixin for the Cylc "views", however, I don't know whether a mixin can create the context-menu component either.
  • A wrapper component that the Cylc "views" are placed inside. The interact method would be passed to the view which would in turn pass it down to whatever components require it.

Copy link
Member

Choose a reason for hiding this comment

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

Basic premise:

If you've got something you wan't to be interactive, wrap it with a and provide it the Cylc ID for the "thing" your element represents.

This wrapper handles context menus, pop-ups and mutations.

I did think about adding this functionality into the task/job components themselves which would make developing new views a little easier, however, this allows groups of components to be wrapped up together.

Sounds like a good approach, similar to the Vuetify v-skeleton component. It wraps the component(s) and adds it some new feature.

I'm not 100% happy with this approach. For example, what if you wanted to wrap an entire row in the tree view? You could do it, however, CylcObject and the Tree components would be fighting for interaction events.

Hmmm, well thought. But I think you should be able to capture the click or other events in the CylcObject, while allowing the children components to receive the event too, if necessary.

Also this really won't work with multiple selection etc. I think for that to work this functionality would have to be much more centralised. Perhaps this is the stepping stone.

If I understand correctly, the issue would be if we allowed users to select a cycle point and a family proxy at the same time. What would happen when the user click to display the mutations? If so, I think we can just disable the mutations pop up when multiple elements of different types are selected (might be tricky to do that, but doable I think).

In Vue terms this could be:

A directive, however, I don't know whether a directive can create the context-menu component.

Hmm, that would be tricky. Not sure how/where I would start to implement this with a directive. I haven't used any custom directive yet, but from what I've used in other components, it's normally used to enable/disable a feature of a component. So the components (TreeItem for example) would have to have built-in support to mutations, and that directive would just enable/disable it.

I think the current approach keeps a better separation of concerns, but might be wrong.

A mixin for the Cylc "views", however, I don't know whether a mixin can create the context-menu component either.

Yeah, I can't think of a clean way, without making each component aware of the context-menu, at least in the template section we would need to have some if/elses.

A wrapper component that the Cylc "views" are placed inside. The interact method would be passed to the view which would in turn pass it down to whatever components require it.

That could work, but not sure if that'd work with every sub-component. Worth a try if you think that'd be better. At least I think that's the easiest of the three options you suggested.

But I liked the simple CylcObject wrapper 😃

@oliver-sanders
Copy link
Member Author

oliver-sanders commented Oct 1, 2020

Ok, getting close with this, tidied and rebased, need to get started on tests.

One blocker is that in the CylcObject component all of the computed properties are calculated on load (and presumably all of the components too). What's the best way to delay this until the component is interacted with?

@kinow
Copy link
Member

kinow commented Oct 1, 2020

What's the best way to delay this until the component is interacted with?

If you only need to calculate the values once, I think it would be to use the data attributes in the UI elements, and have a method under methods that is responsible for populating the data attributes.

But if you still want reactivity, you can try with a watch value, and see if you can control the reactivity. If you don't set immediate: true (I think default is false anyway?), the watcher is not executed when the component loads.

There are cases when you need to mix watch with a method too (I think we have that in the new filters?)

@oliver-sanders
Copy link
Member Author

Ok, I've added a state variable, sadly this means it's no longer a functional component but at last it's not performing un-necessary computation on mount.

Comment on lines 245 to 246
// TODO: fixme
// expect(taskProxy.$children[0].filtered).to.equal(false)
Copy link
Member Author

Choose a reason for hiding this comment

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

I couldn't work out how to fix these, not sure how to debug. I think this is failing because I've added extra components into the hierarchy?

Copy link
Member

Choose a reason for hiding this comment

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

That would make sense. Same happened when I added that skeleton loader wrapping components in Lumino widgets.

@codecov-commenter
Copy link

codecov-commenter commented Oct 2, 2020

Codecov Report

Merging #504 into master will increase coverage by 1.73%.
The diff coverage is 83.15%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #504      +/-   ##
==========================================
+ Coverage   66.27%   68.01%   +1.73%     
==========================================
  Files          43       45       +2     
  Lines         940     1091     +151     
  Branches       59       59              
==========================================
+ Hits          623      742     +119     
- Misses        299      331      +32     
  Partials       18       18              
Flag Coverage Δ
#unittests 68.01% <83.15%> (+1.73%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
src/components/cylc/Mutation.vue 15.78% <ø> (ø)
src/components/cylc/tree/TreeItem.vue 69.23% <ø> (ø)
.../components/graphqlFormGenerator/FormGenerator.vue 69.56% <ø> (ø)
...omponents/graphqlFormGenerator/components/List.vue 33.33% <ø> (ø)
src/services/workflow.service.js 0.00% <0.00%> (ø)
src/utils/graphql.js 91.30% <ø> (-5.47%) ⬇️
src/components/cylc/cylcObject/CylcObject.vue 37.50% <37.50%> (ø)
src/utils/aotf.js 89.08% <89.08%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update fe099b5...84d00d1. Read the comment docs.

@oliver-sanders oliver-sanders mentioned this pull request Oct 2, 2020
11 tasks
Copy link
Member

@kinow kinow left a comment

Choose a reason for hiding this comment

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

Haven't tested yet, but read the code in GitHub UI, and it is looking good 👍 Few TODO/notes that I think you are working on, so will leave the review for later 👍

@oliver-sanders
Copy link
Member Author

Rebased and brought in support for mutating cycle points thanks to #505.

@oliver-sanders oliver-sanders marked this pull request as ready for review November 11, 2020 15:53
@oliver-sanders
Copy link
Member Author

oliver-sanders commented Nov 11, 2020

Ok, I think I'll open this now, it needs changes, but it'll take me a fair amount of research to work out how to move forward so perhaps better to do this in stages.

This approach could be used in GScan right away but I would suggest holding off as it may well change.

Copy link
Member

@kinow kinow left a comment

Choose a reason for hiding this comment

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

Tested functionally, and it worked fine! 👍 Going to look at the code now.

I think I clicked on set-outputs while another task was running, and ended up with:

image

Which I think means the workflow cannot make any progress 😆 no idea how that happened, but my bad, as five is quite fast, and I was simply clicking on the icons to test and see how the UI reacted 👍

Copy link
Member

@kinow kinow left a comment

Choose a reason for hiding this comment

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

👍 looks good to me! Found no issues in the code, only one question there about the type of some object. There are some TODO's and commented out tests. Let me know if you need help with any of those.


loadMutations () {
// TODO: this assumes all workflows use the same schema which is and
// isn't necessarily true, not quite sure, come back to this later
Copy link
Member

Choose a reason for hiding this comment

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

👍


/*
* Functionality for introspecting mutations, associating them with Cylc
* objects, filtering and calling mutatons.
Copy link
Member

Choose a reason for hiding this comment

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

s/mutatons/mutations

* true if this field must be provided for the mutation to be called.
*
* @param {object} mutation - One Mutation as returned by introspection query.
* @param {object} types - Types as returned by introspection query.
Copy link
Member

Choose a reason for hiding this comment

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

Is it object or Array? Further down we call arg._default = getNullValue(arg.type, types). In getNullValue there's a for (const type of types) {… if an Object is given, it would—I think—fail with Uncaught TypeError: types is not iterable (not sure how to test it from the UI, sorry)

@kinow
Copy link
Member

kinow commented Nov 12, 2020

The Bootstrap.vue Toast component could serve as reference @oliver-sanders for a different approach for the CylcObject and the Vuetify menu: https://bootstrap-vue.org/docs/components/toast

You create a "toast" with

        this.$bvToast.toast(`Toast ${this.counter} body content`, {
          title: `Toaster ${toaster}`,
          toaster: toaster,
          solid: true,
          appendToast: append
        })

From any part or your code. That creates a popup with an alert/notification in different parts of the screen. We would just need to make it appear relative to the component that originated the call.

The toast component also uses a plug-in. In Vue we can create a plugin that is executed once when the application installs the plug-in, and loads directives, components, creates global singletons, etc.

e.g.

import CylcObject from '@/components/cylc/cylcObject/CylcObject'

/**
 * Cylc Objects plug-in.
 */
export default {
  /**
   * Called when the Vue application is created, and this plug-in is loaded.
   * @param Vue {object} - Vue application
   * @param options {*} - options passed to the plug-in (if any)
   */
  install (Vue, options) {
    // We could...

    // register the CylcObject component globally (no need to import)
    Vue.component('cylc-object', CylcObject)

    // add a global directive
    Vue.directive('cylc-object', {
      bind (el, binding, vnode, oldVnode) {
        // define that a cylc-object will have an @click and automagically
        // trigger an event?
      }
    })

    // create a singleton in the application to listen for events
    Vue.prototype.$cylcObjectsMenu = {}
  }
}

Take a look later if that looks like a promising path. If so I can dig a bit more to try to come up with a PR to your PR that

  • removes the 1 Vuetify Menu for each CylcObject
  • creates a global menu when a view has some component/directive/etc?
  • makes CylcObject trigger the global menu to be displayed when clicked
  • positions the menu relative to the component that triggered the global menu

@oliver-sanders
Copy link
Member Author

Sounds promising!

@kinow
Copy link
Member

kinow commented Nov 15, 2020

Before trying anything, I rebased, then yarn run build --optimize on this branch. Found no memory leaks, and the garbage collection is working fine. On my computer, the heap memory is going between ~13 and ~18MB, using workflow five.

image

image

image

On master the performance is similar, but the browser spent less time on Scripting.

image

Nothing major, but having minus one component per TreeItem could help to keep the Scripting time down even with complex.

@kinow
Copy link
Member

kinow commented Nov 16, 2020

I've got the conflict fixed locally, and also the directive is partly done. Tomorrow will continue, now working on breaking CylcObject into a global v-menu that can be added per view/component basis, and another code to react to @click on elements with v-cylc-object directive.

Copy link
Member

@kinow kinow left a comment

Choose a reason for hiding this comment

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

CI errors might be due to having to run yarn install.

@kinow
Copy link
Member

kinow commented Dec 7, 2020

@oliver-sanders CI failed again, sorry. I just checked in the GH UI, and I think package.json is missing from this PR. But it was included in the PR to your repo - https://github.com/oliver-sanders/cylc-ui/pull/3/files.

I think a merge/rebase accident happened somewhere, and the dependency was removed. Probably copying that dependency and re-running yarn install will fix it 🤞

Copy link
Member

@kinow kinow left a comment

Choose a reason for hiding this comment

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

Worked with no issues. I tested with five, and everything appears to have worked fine. JS code looks good! 🚀 thanks @oliver-sanders

Copy link
Member

@hjoliver hjoliver left a comment

Choose a reason for hiding this comment

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

Looks great 🎉 I did some pretty extensive testing, found no problems.

@hjoliver
Copy link
Member

hjoliver commented Dec 8, 2020

Sounds like any remaining work is to be down in follow-up PRs, so this is going in 🚀

@hjoliver hjoliver merged commit 5c8c5be into cylc:master Dec 8, 2020
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.

5 participants