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

[KED-2770] - Expose related dataset nodes for focus mode flowchart #530

Merged
merged 127 commits into from
Aug 10, 2021

Conversation

studioswong
Copy link
Contributor

Description

related ticket: https://jira.quantumblack.com/browse/KED-2770

This is the work to expose the related input and output nodes and display them with a dotted outline and edges. ( see below). Those nodes are displayed for indicative purposes only, where all typical click interations for nodes on the flowchart and sidebar will remain disabled for those input and output nodes.

image

image

image

Development notes

The main changes to enable this feature includes:

  • updating the getNodeDisabledModularPipeline selector so that it will check for input and output nodes related to the selected modular pipeline and enable that node
  • constructing selectors to return the list of input / output nodes and edges for reference
  • refactoring the focusMode state from a local state to a global state value stored under the visible prop
  • refactoring node-list to take in the new global state for focusMode and the related selectors for the input / output nodes and edges
  • added in new actions to allow the update of the focusMode global state
  • adding in the new css classes for the new display of those input / output nodes
  • adding in new changes for the flowchart draw function to assign the newly added css classes for those input / output nodes on the flowchart
  • Adding in checks for the sidebar node-list to check for these input / output nodes in focus mode and ensure the nodes remains disabled on the sidebar
  • added all relevant tests for the newly added selectors, actions, and checks throughout the codebase

I have also added in some minor refactor of the focus mode icon.

Please note that, due to the latest decision of removing the newparams flag, I have removed all considerations of styling for when newparams flag is off ( i.e old version of parameters).

QA notes

Please play around with the focus mode and toggle between filters to test the display of those input / output nodes.

Checklist

  • Read the contributing guidelines
  • Opened this PR as a 'Draft Pull Request' if it is work-in-progress
  • Updated the documentation to reflect the code changes
  • Added new entries to the RELEASE.md file
  • Added tests to cover my changes

Legal notice

  • I acknowledge and agree that, by checking this box and clicking "Submit Pull Request":

  • I submit this contribution under the Apache 2.0 license and represent that I am entitled to do so on behalf of myself, my employer, or relevant third parties, as applicable.

  • I certify that (a) this contribution is my original creation and / or (b) to the extent it is not my original creation, I am authorised to submit this contribution on behalf of the original creator(s) or their licensees.

  • I certify that the use of this contribution as authorised by the Apache 2.0 license does not violate the intellectual property rights of anyone else.

@@ -370,4 +376,17 @@ describe('Selectors', () => {
expect(Object.keys(nodesWithInputParams)).toEqual(parameterNodesID);
});
});

describe('getInputOutputDataNodes', () => {
it('includes input output ndoes related to a modular pipeline in the returned object', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

typo - nodes

Copy link
Collaborator

@limdauto limdauto Aug 9, 2021

Choose a reason for hiding this comment

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

Suggestion: You can use github suggestion to suggest changes for these small issues like this:

Suggested change
it('includes input output ndoes related to a modular pipeline in the returned object', () => {
it('includes input output nodes related to a modular pipeline in the returned object', () => {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks both - great spot! I've updated the typo in my latest commit

Copy link
Collaborator

@limdauto limdauto left a comment

Choose a reason for hiding this comment

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

This PR is 🔥 Everything seems very clear to me. Amazing work @studioswong Just a few minor nitpicks.

I will do a bit more testing but the code looks good to me.

if (changed('edges')) {
enterEdges
.append('path')
if (changed('edges', 'newParamsFlag', 'focusMode', 'inputOutputDataNodes')) {
Copy link
Collaborator

@limdauto limdauto Aug 9, 2021

Choose a reason for hiding this comment

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

qq: do you think we should remove newParamsFlag from this PR since its removal has already been merged? Or should that be a follow up PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can remove this in this PR :) thanks for the reminder!

.attr('marker-end', (edge) =>
edge.sourceNode.type === 'parameters'
? `url(#pipeline-arrowhead--accent)`
? focusMode !== null && inputOutputDataEdges[edge.id]
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: I think giving focusMode !== null && inputOutputDataEdges[edge.id] a name might help with this block as it's repeated twice in this nested tenary operator. You can also reuse that variable further down as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sounds good, I'll extract that out.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah agreed nested ternary ends up being pretty difficult to follow, so it's a probably a good idea to split this one up e.g. some variables / string templates, if that makes sense

(edge) => edge.sourceNode.type === 'parameters'
(edge) =>
edge.sourceNode.type === 'parameters' &&
!inputOutputDataEdges[edge.id]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry I don't understand why this condition !inputOutputDataEdges[edge.id] is needed here. Is it to fix the problem you identified the other day?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this condition is needed to prevent applying this class to parameter nodes that are also input/output nodes - if this check is not applied this will create potential conflicts with the css class that will be applied to parameter nodes that are part of input/output. ( i.e taking this check out will result in some input/output parameter nodes not adopting the pipeline-edge--parameters--input class. )

);
});

it('applies pipeline-edge--data--input class to input dataset edges under focus mode', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: shouldn't this modifier be named as pipeline-edge--dataset--input to be consistent with pipeline-node--dataset-input (or the other way around)? Same comment about pipeline-edge--parameters--input and pipeline-node--parameter-input (although I have a feeling this naming is intentional because pipeline-edge might have more than 1 parameters, right?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure, I can rename this as pipeline-edge--dataset--input to align this with the naming for the input nodes css class

@@ -361,6 +415,9 @@ describe('FlowChart', () => {
visibleSidebar: expect.any(Boolean),
visibleCode: expect.any(Boolean),
visibleMetaSidebar: expect.any(Boolean),
inputOutputDataNodes: expect.any(Object),
inputOutputDataEdges: expect.any(Object),
focusMode: expect.any(Object),
Copy link
Collaborator

Choose a reason for hiding this comment

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

This set of tests is awesome. Very clear!!! 🏆

return true;

// Hide nodes that don't have at least one modular pipeline filter enabled
return !isNodeOfActiveModularPipeline(
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems duplicated with the earlier if check. Should we short circuit by moving it up instead?

if (!isNodeOfActiveModularPipeline(
      nodeModularPipelines,
      nodeID,
      modularPipelineEnabled
    ) || (nodeType[nodeID] === 'task') {
   return false;
}

Copy link
Contributor Author

@studioswong studioswong Aug 10, 2021

Choose a reason for hiding this comment

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

Thanks for the suggestions - I had a look into this, and while the arrangement of the returned statement would stand as is (i.e we still need to return the result of the check and this cannot be combined with the check for the task node), I had extracted the check for isNodeOfActiveModularPipeline out to a variable so the check will not be duplicated and allows more readability. I had also added further comments to explain the exceptions that the if statements filters out before returning the result of this check as the final returned value. Hopefully this will be more clear and the reason behind this arranhement would be more apparent to you.

Copy link
Contributor

@rashidakanchwala rashidakanchwala left a comment

Choose a reason for hiding this comment

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

Hi Susanna, Great work! it work's well for me!

Copy link
Contributor

@bru5 bru5 left a comment

Choose a reason for hiding this comment

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

Works really nicely! Great stuff 👏

Still a bit more for me to check out, but for now I've left some suggestions if you're interested, mostly around simplifying a few parts, if it makes sense.

.attr('marker-end', (edge) =>
edge.sourceNode.type === 'parameters'
? `url(#pipeline-arrowhead--accent)`
? focusMode !== null && inputOutputDataEdges[edge.id]
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah agreed nested ternary ends up being pretty difficult to follow, so it's a probably a good idea to split this one up e.g. some variables / string templates, if that makes sense

!inputOutputDataEdges[edge.id]
)
.classed(
'pipeline-edge--parameters--input',
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to check since there's two -- in this classname is that to say there are two independent modifiers here? If so maybe consider splitting into two classnames I think to get more flexibility?

@@ -69,7 +79,13 @@ const NodeListProvider = ({
if (isGroupType(item.type) || isModularPipelineType(item.type)) {
onGroupItemChange(item, item.checked);
if (isModularPipelineType(item.type)) {
onToggleFocusMode(item);
if (isModularPipelineType(item.type)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like this check might have been duplicated accidentally here?

@@ -84,7 +100,11 @@ const NodeListProvider = ({
if (isGroupType(item.type) || isModularPipelineType(item.type)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Thinking it might be a good idea at this point to split this into two separate blocks, to help avoid some nesting if possible?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had a look at this, and given that both conditions calls onGroupItemChange(item, checked);, I think it is still better to combine that into a single if statement. The nesting is still readable, and I would rather avoid duplicate code.

@@ -269,14 +274,16 @@ export const getFilteredNodeItems = createSelector(
const disabled =
node.disabled_tag ||
node.disabled_type ||
node.disabled_modularPipeline;
node.disabled_modularPipeline ||
(focusMode !== null && !!inputOutputDataNodes[node.id]);
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess focusMode && inputOutputDataNodes[node.id] can maybe be ok here (unless 0 is truthy for this)?

Copy link
Contributor Author

@studioswong studioswong Aug 10, 2021

Choose a reason for hiding this comment

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

I am glad you raised this question - I am using !!inputOutputDataNodes[node.id] to force disabled to return a boolean value under this check. If we use inputOutputDataNodes[node.id] the check will return an error. ( you can also verify this by trying this out, and you will see what I meant ).

@@ -42,6 +42,10 @@
height: $row-icon-size;
fill: var(--color-text);

&.pipeline-row__toggle-icon--focus-checked{
Copy link
Contributor

Choose a reason for hiding this comment

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

Might just need a space adding near the end here

/**
* Calculate whether nodes should be disabled based on their modular pipelines
* Calculate whether nodes should be disabled based on their modular pipelines,
* except related dataset nodes and
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe the comment got a bit cut off at the end here?

nodeID,
modularPipelineEnabled
) &&
(nodeType[nodeID] === 'parameters' || nodeType[nodeID] === 'data')
Copy link
Contributor

Choose a reason for hiding this comment

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

If the isNodeOfActiveModularPipeline might get a little expensive, then could be a good idea to move these simpler conditions to be checked first, to help reduce the cost?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've refactored this check into a separate constant variable - please see my latest commit

@@ -48,10 +66,17 @@ export const getTransitiveEdges = createSelector(
return;
}
const target = edgeTargets[edgeID];

// Further filter out connections between indicative input / output nodes under focus mode
const isNotInputEdge =
Copy link
Contributor

Choose a reason for hiding this comment

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

Wondering if it's worth considering flipping this to the positive e.g. isInputEdge to help simplify the condition (unless it's a performance thing I suppose?)

Copy link
Contributor Author

@studioswong studioswong Aug 10, 2021

Choose a reason for hiding this comment

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

It is a performance thing as checking isNotInputEdge is more straight forward and has less cases rather than the positive situation ( this is apparent if you read through the condition - checking for isInputEdge has much more possibility rather than checking for isNotInputEdge. )

Furthermore, I don't really see the upside of the refactor work for flipping this to positive?

if (focusedModularPipeline !== null) {
visibleEdges.forEach((edge) => {
if (
!nodeModularPipelines[edge.source]?.includes(
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be worth considering breaking out the conditions here into constants?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This check is only used once in this selector, moreover with it being very straight forward, I believe this is fine for this to stay as is.

@studioswong
Copy link
Contributor Author

Thanks all for the comments! I've gone through all your comments and made the suggested changes.

@studioswong studioswong merged commit 361964b into main Aug 10, 2021
@studioswong studioswong deleted the feature/focus-mode-2.0-merged branch August 10, 2021 19:29
Copy link
Contributor Author

@studioswong studioswong left a comment

Choose a reason for hiding this comment

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

/

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.

4 participants