Skip to content

Commit

Permalink
Merge branch 'main' into docs/datasets-preview-docs-update
Browse files Browse the repository at this point in the history
  • Loading branch information
SajidAlamQB authored Sep 6, 2024
2 parents 2f7dd87 + cc1a119 commit bd79d0d
Show file tree
Hide file tree
Showing 54 changed files with 1,379 additions and 76 deletions.
3 changes: 1 addition & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
"curly": ["error"],
"valid-typeof": ["error"],
"camelcase": "error",
"id-length": ["error", { "min": 3, "exceptions": ["_","a","b","d","e","i","j","k","x","y","id","el","pi","PI","up"] }],
"no-var": ["error"],
"id-length": ["error", { "min": 3, "exceptions": ["_","a","b","d","e","i","j","k","x","y","id","el","pi","PI","up","to"] }], "no-var": ["error"],
"lines-between-class-members": ["error", "always"]
}
}
Expand Down
1 change: 1 addition & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Please follow the established format:
## Major features and improvements

- Introduce `onActionCallback` prop in Kedro-Viz react component. (#2022)
- Slice a pipeline functionality. (#2036)

## Bug fixes and other changes

Expand Down
7 changes: 4 additions & 3 deletions cypress/tests/ui/flowchart/flowchart.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ describe('Flowchart DAG', () => {

beforeEach(() => {
cy.enablePrettyNames(); // Enable pretty names using the custom command
cy.wait(500);
cy.get('.feature-hints__close').click(); // Close the feature hints so can click on a node
cy.wait(500);
});

it('verifies that users can expand a collapsed modular pipeline in the flowchart. #TC-23', () => {
const modularPipelineText = 'feature_engineering';
const taskNodeText = 'Create Derived Features';

cy.enablePrettyNames();
const taskNodeText = 'create_derived_features';

// Assert before action
cy.get('.pipeline-node > .pipeline-node__text').should(
Expand Down
7 changes: 4 additions & 3 deletions cypress/tests/ui/flowchart/menu.cy.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// All E2E Tests Related to Flowchart Menu goes here.

import { prettifyName } from '../../../../src/utils';

describe('Flowchart Menu', () => {
beforeEach(() => {
cy.enablePrettyNames(); // Enable pretty names using the custom command
cy.wait(500);
cy.get('.feature-hints__close').click(); // Close the feature hints so can click on a node
cy.wait(500);
});

it('verifies that users can select a section of the flowchart through the drop down. #TC-16', () => {
Expand Down Expand Up @@ -144,7 +145,7 @@ describe('Flowchart Menu', () => {
.invoke('text')
.then((focusedNodesText) =>
expect(focusedNodesText.toLowerCase()).to.contains(
prettifyName(nodeToFocusText).toLowerCase()
nodeToFocusText
)
);
cy.get('.pipeline-node--active > .pipeline-node__text').should(
Expand Down
3 changes: 2 additions & 1 deletion cypress/tests/ui/flowchart/panel.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ describe('Pipeline Minimap Toolbar', () => {
cy.__waitForPageLoad__(() => {
let initialZoomValue;
let zoomInValue;

cy.get('@zoomScale')
.invoke('text')
.then((text) => {
Expand Down Expand Up @@ -155,6 +155,7 @@ describe('Pipeline Minimap Toolbar', () => {
cy.get('@zoomScale')
.invoke('text')
.should((text) => {
initialZoomValue = parseFloat(text.replace('%', ''));
expect(initialZoomValue).to.be.eq(parseFloat(text.replace('%', '')));
});
});
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/images/slice_pipeline_slice_reset.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Take a look at the <a href="https://demo.kedro.org/" target="_blank" rel="noopen
kedro-viz_visualisation
share_kedro_viz
preview_datasets
slice_a_pipeline
experiment_tracking
```

Expand Down
33 changes: 33 additions & 0 deletions docs/source/slice_a_pipeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Slice a pipeline

Slicing a pipeline in Kedro refers to creating a subset of a pipeline's nodes, which can help in focusing on specific parts of the pipeline. There are two primary ways to achieve this:

1. **Programmatically with the Kedro CLI.** This method is suitable for those comfortable with command-line tools. Detailed steps on how to achieve this are available in the kedro documentation: [Slice a Pipeline](https://docs.kedro.org/en/stable/nodes_and_pipelines/slice_a_pipeline.html).

2. **Visually through Kedro-Viz:** This approach allows you to visually select and slice pipeline nodes, which then generates a run command for executing the slice within your Kedro project.

## Benefits of Kedro-Viz slicing

- **Visual Representation:** View the relationships between nodes and identify which ones are part of your slice.
- **Immediate Command Generation:** Get a ready-to-use CLI command for executing the sliced pipeline.
- **Interactive Control:** Visually select and reset slices with a couple of clicks.

## Steps to slice in Kedro-Viz

Kedro-Viz offers a user-friendly visual interface for slicing pipelines. Follow these steps to use the slicing feature:

1. **Select elements in the flowchart:** In Kedro-Viz, select two elements to set the boundaries for your slice:
- Click on the first node you want to include.
- Hold the Shift key and select the second node.

![](./images/slice_pipeline_multiple_click.gif)

2. **Highlighted selection:** The flowchart will highlight all nodes between the selected elements, and the corresponding nodes in the list on the left will also be highlighted.

3. **View the run command:** After selecting the nodes, Kedro-Viz generates a CLI command for the sliced pipeline. You can copy this command and use it directly in your Kedro project to run the slice.

4. **Slice the pipeline:** When you're ready, click the "Slice" button. This opens a new view where you can directly interact with the sliced pipeline.

5. **Reset:** To discard your selection and return to the full pipeline view, click the "Reset" button. This will clear the slice and restore the default view.

![](./images/slice_pipeline_slice_reset.gif)
24 changes: 17 additions & 7 deletions package/kedro_viz/integrations/kedro/data_catalog_lite.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,20 @@ def from_config(
last_pattern = sorted_patterns.popitem()
user_default = {last_pattern[0]: last_pattern[1]}

return cls(
datasets=datasets,
dataset_patterns=sorted_patterns,
load_versions=load_versions,
save_version=save_version,
default_pattern=user_default,
)
try:
return cls(
datasets=datasets,
dataset_patterns=sorted_patterns,
load_versions=load_versions,
save_version=save_version,
default_pattern=user_default,
)
except TypeError:
# support for Kedro < 0.19.6
# DataCatalog does not have default_pattern
return cls(
datasets=datasets,
dataset_patterns=sorted_patterns,
load_versions=load_versions,
save_version=save_version,
)
2 changes: 1 addition & 1 deletion package/kedro_viz/integrations/kedro/lite_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def _is_relative_import(self, module_name: str, project_file_paths: Set[Path]):
>>> lite_parser_obj._is_relative_import(module_name, project_file_paths)
True
"""
relative_module_path = module_name.replace(".", "/")
relative_module_path = str(Path(*module_name.split(".")))

# Check if the relative_module_path
# is a substring of current project file path
Expand Down
15 changes: 14 additions & 1 deletion package/tests/test_integrations/test_data_catalog_lite.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,25 @@ def data_catalog_lite_from_config(sane_config):


class TestDataCatalogLiteFromConfig:
def test_from_sane_config(self, data_catalog_lite_from_config, dummy_dataframe):
def test_from_sane_config(
self, data_catalog_lite_from_config, dummy_dataframe, sane_config, mocker
):
"""Test populating the data catalog from config"""
data_catalog_lite_from_config.save("boats", dummy_dataframe)
reloaded_df = data_catalog_lite_from_config.load("boats")
assert_frame_equal(reloaded_df, dummy_dataframe)

# testing error handling
mocker.patch(
"kedro_viz.integrations.kedro.data_catalog_lite.DataCatalog.__init__",
side_effect=[TypeError, None],
)

try:
DataCatalogLite.from_config(**sane_config)
except TypeError:
pytest.fail("TypeError was not handled by from_config method")

def test_config_missing_type(self, sane_config):
"""Check for no error if type attribute is missing for some data set(s)
in the config"""
Expand Down
7 changes: 4 additions & 3 deletions src/actions/pipelines.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getUrl } from '../utils';
import loadJsonData from '../store/load-data';
import { preparePipelineState } from '../store/initial-state';
import {parseUrlParameters, preparePipelineState} from '../store/initial-state';
import { resetData } from './index';

/**
Expand Down Expand Up @@ -99,14 +99,15 @@ export function loadInitialPipelineData() {
// obtain the status of expandAllPipelines to decide whether it needs to overwrite the
// list of visible nodes
const expandAllPipelines = state.expandAllPipelines;
const urlParams = parseUrlParameters();
let newState = await loadJsonData(url).then((data) =>
preparePipelineState(data, true, expandAllPipelines)
preparePipelineState(data, true, expandAllPipelines, urlParams)
);
// If the active pipeline isn't 'main' then request data from new URL
if (requiresSecondRequest(newState.pipeline)) {
const url = getPipelineUrl(newState.pipeline);
newState = await loadJsonData(url).then((data) =>
preparePipelineState(data, false, expandAllPipelines)
preparePipelineState(data, false, expandAllPipelines, urlParams)
);
}
dispatch(resetData(newState));
Expand Down
28 changes: 28 additions & 0 deletions src/actions/slice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export const APPLY_SLICE_PIPELINE = 'APPLY_SLICE_PIPELINE';

export const applySlicePipeline = (apply) => {
return async function (dispatch) {
dispatch({
type: APPLY_SLICE_PIPELINE,
apply,
});
};
};

export const SET_SLICE_PIPELINE = 'SET_SLICE_PIPELINE';

export const setSlicePipeline = (from, to) => {
return async function (dispatch) {
dispatch({
type: SET_SLICE_PIPELINE,
slice: { from, to },
});
};
};

export const RESET_SLICE_PIPELINE = 'RESET_SLICE_PIPELINE';

export const resetSlicePipeline = () => ({
type: RESET_SLICE_PIPELINE,
slice: { from: null, to: null },
});
2 changes: 1 addition & 1 deletion src/components/app/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
--color-bg-1: #{colors.$white-800};
--color-bg-2: #{colors.$grey-0};
--color-bg-3: #{colors.$white-200};
--color-bg-4: #{colors.$white-0};
--color-bg-4: #{colors.$white-200};
--color-bg-5: #{colors.$white-600};
--color-bg-alt: #{colors.$black-700};
--color-bg-list: #{colors.$white-100};
Expand Down
1 change: 0 additions & 1 deletion src/components/app/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { localStorageName } from '../../config';
import { prepareNonPipelineState } from '../../store/initial-state';
import reducer from '../../reducers/index';
import { TOGGLE_GRAPH_LOADING } from '../../actions/graph';
import { prettifyName } from '../../utils/index';

describe('App', () => {
const getState = (wrapper) => wrapper.instance().store.getState();
Expand Down
7 changes: 4 additions & 3 deletions src/components/flowchart-wrapper/flowchart-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,17 @@ export const FlowChartWrapper = ({

if (nodeId) {
const modularPipeline = nodes[nodeId];
const hasModularPipeline = modularPipeline?.length > 0;
const modularPipelineTree = modularPipelinesTree[modularPipeline];
const isModularPipelineChild =
modularPipelineTree?.children?.includes(nodeId);

const isParameterType =
graph.nodes &&
graph.nodes.find(
(node) => node.id === nodeId && node.type === 'parameters'
);

if (hasModularPipeline && !isParameterType) {
if (isModularPipelineChild && !isParameterType) {
onToggleModularPipelineExpanded(modularPipeline);
}
onToggleNodeSelected(nodeId);
Expand Down Expand Up @@ -237,7 +239,6 @@ export const FlowChartWrapper = ({
*/
useEffect(() => {
const isGraphEmpty = Object.keys(graph).length === 0;

if (
(graphRef.current === null || usedNavigationBtn || isInvalidUrl) &&
!isGraphEmpty
Expand Down
38 changes: 38 additions & 0 deletions src/components/flowchart/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ const toSinglePoint = (value) => parseFloat(value).toFixed(1);
*/
const limitPrecision = (path) => path.replace(matchFloats, toSinglePoint);

/**
* Creates a mapping of node IDs to a boolean indicating if the node ID is included in the given values.
* @param {Array} nodes - Array of nodes to process.
* @param {Array} values - Array of values to check against node IDs.
* @returns {Object} An object mapping node IDs to booleans.
*/
function createNodeStateMap(nodes, values) {
const valueSet = new Set(values); // Convert to Set for efficient lookup
return nodes.reduce((acc, { id }) => {
acc[id] = valueSet.has(id);
return acc;
}, {});
}

/**
* Render layer bands
*/
Expand Down Expand Up @@ -135,7 +149,20 @@ export const drawNodes = function (changed) {
nodes,
focusMode,
hoveredFocusMode,
isSlicingPipelineApplied,
} = this.props;
const {
from: slicedPipelineFromId,
to: slicedPipelineToId,
range,
} = this.state.slicedPipelineState;

const slicedPipelineFromTo =
slicedPipelineFromId &&
slicedPipelineToId &&
createNodeStateMap(nodes, [slicedPipelineFromId, slicedPipelineToId]);

const slicedPipelineRange = createNodeStateMap(nodes, range);

const isInputOutputNode = (nodeID) =>
focusMode !== null && inputOutputDataNodes[nodeID];
Expand Down Expand Up @@ -241,6 +268,17 @@ export const drawNodes = function (changed) {
allNodes
.classed('pipeline-node--active', (node) => nodeActive[node.id])
.classed('pipeline-node--selected', (node) => nodeSelected[node.id])
.classed(
'pipeline-node--sliced-pipeline',
(node) => !isSlicingPipelineApplied && slicedPipelineRange[node.id]
)
.classed(
'pipeline-node--from-to-sliced-pipeline',
(node) =>
!isSlicingPipelineApplied &&
slicedPipelineFromTo &&
slicedPipelineFromTo[node.id]
)
.classed(
'pipeline-node--collapsed-hint',
(node) =>
Expand Down
Loading

0 comments on commit bd79d0d

Please sign in to comment.