-
Notifications
You must be signed in to change notification settings - Fork 27
Architecture
Diagrams about Cylc UI architecture. Useful for other developers, not intended for wider audience.
- Building blocks
- How are components organized in the UI hierarchy?
- UI Views
- The UI data store
- GraphQL subscriptions
The following diagram illustrates the stack used to build Cylc UI. The main library, although not highlighted in the figure, is Vue. Cylc UI is a ES6 Vue application, built with vue-cli
.
NOTE: normally you would be working with the webpack, rollerjs, etc, configuration files. But with
vue-cli
the configuration is stored invue.config.js
andvue-cli
will execute webpack/babel/etc for you. The configuration can be kept invue.config.js
(e.g. we don't have webpack elsewhere) or in an external file (e.g. we have babel configuration inbabel.config.js
). It's up to the developers to choose.
It is easier to read the diagram from the bottom up.
At the bottom of the stack we have the transpiler Babel, which does things like rewriting const nodeId = node.node?.id
(null coalescing operator, not supported in browsers) to something like const nodeId = (node.node) ? node.node.id : undefined
. It also transpiles code from libraries like Vuetify (see babel.config.js
).
webpack is the bundler used by our code. It coordinates babel
, sass
, postcss
, etc., producing the final files of our application.
It is worth opening the GitHub README.md of each of these building tools, if not already familiar with any of them.
In the middle of the diagram we have libraries. At the top of the libraries panel we have Vue libraries such as Vue, VueRouter (navigation), Vuex (UI data store), and Vuetify (UI framework). These are closely related to Vue. Next we have utility libraries such as lodash
(utilities not found in the JS language), graphql
(parsing & merging queries), enumify
(because ES6 doesn't have enums), and @mdi/js
(material design icons for JS). The penultimate row from the top has just mitt
(event bus) and Lumino
(tabbed layout). Finally, we have the two libraries used for communication. Axios is used for REST (e.g. JupyterHub REST API) and ApolloClient is used for GraphQL (e.g. GraphiQL, mutations, subscriptions.)
At the top of the stack we have the UI components. We used Vuetify to bootstrap the UI development, slowly adding custom components. On the left hand side you have the Vue components we wrote. Some of these components still use Vuetify components and styling (i.e. css).
To the right you have a few Vuetify components that are commonly used.
NOTE: you may not find the imports of Vuetify components, but that's because we have
vuetify-loader
, which takes care to create the imports on-the-fly as needed. With this, you can simply write<v-list>
in your template and theVList
will be automagically imported. In theory it should support tree-shaking too, but both Vuetify and Lodash were never very well trimmed.
This diagram shows an example of how the UI components are organized when you access the route /#/
(the application dashboard) in your browser. Take a look at src/router/index.js
to see what else VueRouter is doing. And for the layout, look at the App.vue
computed variables.
The VMain
component is not in our source code directory. It was left intentionally to show that Alert.vue
and Dashboard.vue
both share a parent, and also to point that we have several components in the UI structure that are imported from Vuetify.
The best way to visualize the complete list of components and how they are organized hierarchically is using the Vue Dev Utils browser extension. Note in the Vue Dev Utils screenshot below that it displays the same structure above, but with more components.
NOTE: there are two Vue extensions in the screenshot above, one for Vue 2 and one for Vue 3. Pay attention to which version you install. Cylc UI is built with Vue 2.
This section revolves around the Workflow.vue
view and the WorkflowService
. That view utilizes the Lumino.vue
component to show other views in tabs.
NOTE: Cylc 7 had the concept of Views, like the Tree View, the Graph View, the Dot View, and so it goes. In Cylc 8 UI, the Vue framework has the concept of VueRouter's views. To add to the confusion, the Cylc 7 Tree View is implemented as a Vue component, that is used in a VueRouter view. So note that these terms may be used interchangeably.
The WorkflowService
drives the subscription for a Cylc UI View.
The Cylc 8 UI views are VueRouter views (although Vue components are also supported) and use Vue mixins to define the expected behavior of Views. The mixins will trigger functions in the WorkflowService
to start and stop subscriptions.
Not all Cylc UI views need to have a GraphQL subscription. The Mutation
view, for instance, does not have one. If a view does not need GraphQL, it will require fewer mixins.
A view like the Tree View, that contains one GraphQL subscription to fetch data to build the tree, will use the following mixins:
-
mixins/index
for the page title (this is for when the view is accessed directly) -
mixins/graphql
to define the GraphQL subscription variables (e.g.workflowId: some/workflow/run1
) -
mixins/subscriptionComponent
hooks up the Vue component lifecycle hooks withWorkflowService
functions -
mixins/subscriptionView
hooks up the VueRouter navigation guards withWorkflowService
functions -
mixins/subscription
this mixins is used by the twosubscription*
mixins above, and adds theViewState
state andsetAlert
function to the view
Each Cylc UI View has a ViewState
that may hold the state of NO_STATE
, LOADING
, ERROR
, or COMPLETE
. The mixins/subscription
has a computed isLoading
property that uses the ViewState
to return a boolean. The WorkflowService
updates the ViewState
.
Finally, views (or components) also need to define the query
property, in either data or computed sections. This property must have the type model/SubscriptionQuery
. A SubscriptionQuery
contains:
- A GraphQL Query (e.g.
subscription OnWorkflowTreeDeltasData ($workflowId: ID) { ... }
, seegraphql/queries.js
) - An object with the GraphQL variables (probably computed via the
mixins/graphql
, may be empty e.g. GScan query) - A name (GScan uses a query named
root
, while the TreeView usesworkflow
) - A list of action names or callbacks (Vuex action/mutation list executed for each GraphQL response message)
- A list of tear down action names (for when the View or component is removed/destroyed, to clear the UI store, etc)
When you navigate to a URL like /#/tree/:workflowId
the Cylc Tree View will be a VueRouter view. It will use its navigation guards beforeRouteEnter
to start a GraphQL subscription using the Tree
view query
property. When you type a new URL in your browser like /#/tree/:anotherWorkflowId
Vue will re-use the component, updating it, but VueRouter view will update the subscription too via the beforeRouteUpdate
navigation guard. Finally, when you leave the VueRouter view, then it will stop the GraphQL subscription (via the Vue component lifecycle function beforeDestroy
.)
When you navigate to a URL like /#/workflows/:workflowId
the Cylc Workflow View will be a VueRouter View. It will also use the navigation guards to tell the WorkflowService
to start the subscriptions. However, the Workflow View does not have a query
property. Instead, it uses the JupyterLab Lumino
component functions to capture when a tab with a View is added or removed, calling the functions to update the subscriptions. So the Lumino
actions mimic what the navigation guards and Vue component lifecycle functions do.
This diagram gives a high level overview of the Cylc UI data store (Vuex states only).
To understand how Vuex works, what are states, actions, and mutations, it is recommended the reading of the following Vuex docs sections: