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

Add filtering of pipelines on Kedro-Viz flowchart in VSCode #2269

Merged
merged 22 commits into from
Mar 6, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Design review fix
Signed-off-by: Jitendra Gundaniya <jitendra_gundaniya@mckinsey.com>
jitu5 committed Mar 3, 2025
commit 3de37fdf9ea9c187ca8800918c93d40272bc259b
Original file line number Diff line number Diff line change
@@ -6,15 +6,13 @@ import {
toggleSidebar,
toggleTextLabels,
toggleExpandAllPipelines,
togglePipelineFilter,
toggleOrientation,
} from '../../actions';
import { toggleModularPipelinesVisibilityState } from '../../actions/modular-pipelines';
import IconButton from '../ui/icon-button';
import LabelIcon from '../icons/label';
import ExportIcon from '../icons/export';
import LayersIcon from '../icons/layers';
import FilterIcon from '../icons/filter';
import LeftRightIcon from '../icons/left-right';
import TopBottomIcon from '../icons/top-bottom';
import PrimaryToolbar from '../primary-toolbar';
@@ -34,7 +32,6 @@ export const FlowchartPrimaryToolbar = ({
onToggleLayers,
onToggleSidebar,
onToggleTextLabels,
onTogglePipelineFilter,
textLabels,
visible,
display,
@@ -60,15 +57,6 @@ export const FlowchartPrimaryToolbar = ({
display={display}
dataTest={`sidebar-flowchart-visible-btn-${visible.sidebar}`}
>
<IconButton
ariaLabel={`Open pipeline filter`}
className={'pipeline-menu-button--labels'}
dataTest={`sidebar-flowchart-filter-btn-${display.filterBtn}`}
icon={FilterIcon}
labelText={`Open pipeline filter`}
onClick={() => onTogglePipelineFilter()}
visible={display.filterBtn}
/>
<IconButton
active={textLabels}
ariaLabel={`${textLabels ? 'Hide' : 'Show'} text labels`}
@@ -153,9 +141,6 @@ export const mapDispatchToProps = (dispatch) => ({
onToggleSidebar: (visible) => {
dispatch(toggleSidebar(visible));
},
onTogglePipelineFilter: () => {
dispatch(togglePipelineFilter());
},
onToggleTextLabels: (value) => {
dispatch(toggleTextLabels(Boolean(value)));
},
9 changes: 8 additions & 1 deletion src/components/sidebar/sidebar.js
Original file line number Diff line number Diff line change
@@ -6,14 +6,19 @@ import MiniMap from '../minimap';
import MiniMapToolbar from '../minimap-toolbar';
import NodesPanel from '../nodes-panel';
import PipelineList from '../pipeline-list';
import ToolbarFilterButton from '../toolbar-filter-button';

import './sidebar.scss';

/**
* Main app container. Handles showing/hiding the sidebar nav, and theme classes.
* @param {Boolean} props.visible Whether the sidebar is open/closed
*/
export const Sidebar = ({ displayGlobalNavigation, visible }) => {
export const Sidebar = ({
displayGlobalNavigation,
visible,
displayFilterBtn,
}) => {
const [pipelineIsOpen, togglePipeline] = useState(false);

return (
@@ -29,6 +34,7 @@ export const Sidebar = ({ displayGlobalNavigation, visible }) => {
<NodesPanel faded={pipelineIsOpen} />
</div>
<nav className="pipeline-toolbar">
{displayFilterBtn && <ToolbarFilterButton />}
<FlowchartPrimaryToolbar />
<MiniMapToolbar />
</nav>
@@ -41,6 +47,7 @@ export const Sidebar = ({ displayGlobalNavigation, visible }) => {
const mapStateToProps = (state) => ({
displayGlobalNavigation: state.display.globalNavigation,
displaySidebar: state.display.sidebar,
displayFilterBtn: state.display.filterBtn,
visible: state.visible.sidebar,
});

3 changes: 3 additions & 0 deletions src/components/toolbar-filter-button/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ToolbarFilterButton from './toolbar-filter-button';

export default ToolbarFilterButton;
48 changes: 48 additions & 0 deletions src/components/toolbar-filter-button/toolbar-filter-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { connect } from 'react-redux';
import IconButton from '../ui/icon-button';
import FilterIcon from '../icons/filter';
import { togglePipelineFilter } from '../../actions';

import './toolbar-filter-button.scss';

/**
* Filter button component used in the sidebar toolbar
* @param {Boolean} displayFilterBtn Whether the filter button should be displayed
* @param {Function} onTogglePipelineFilter Handler for filter button click
*/
export const ToolbarFilterButton = ({
displayFilterBtn,
onTogglePipelineFilter,
}) => {
return (
<div className="pipeline-toolbar--filter-container">
<IconButton
ariaLabel={`Open pipeline filter`}
className={'pipeline-menu-button--labels'}
dataTest={`sidebar-flowchart-filter-btn-${displayFilterBtn}`}
icon={FilterIcon}
labelText={`Open pipeline filter`}
onClick={() => onTogglePipelineFilter()}
visible={displayFilterBtn}
container={'div'}
/>
<hr className={'pipeline-toolbar--divider'} />
</div>
);
};

const mapStateToProps = (state) => ({
displayFilterBtn: state.display.filterBtn,
});

export const mapDispatchToProps = (dispatch) => ({
onTogglePipelineFilter: () => {
dispatch(togglePipelineFilter());
},
});

export default connect(
mapStateToProps,
mapDispatchToProps
)(ToolbarFilterButton);
17 changes: 17 additions & 0 deletions src/components/toolbar-filter-button/toolbar-filter-button.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@use '../../styles/variables' as variables;

.kui-theme--light {
--pipeline-toolbar-divider: #{variables.$grey-300};
}

.kui-theme--dark {
--pipeline-toolbar-divider: #{variables.$black-600};
}

.pipeline-toolbar--divider {
border: none;
margin: 0;
width: 100%;
height: 1px;
background-color: var(--pipeline-toolbar-divider);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import ConnectedToolbarFilterButton, {
ToolbarFilterButton,
mapDispatchToProps,
} from './toolbar-filter-button';
import { setup } from '../../utils/state.mock';
import { togglePipelineFilter } from '../../actions';

describe('ToolbarFilterButton', () => {
it('renders without crashing', () => {
const wrapper = setup.mount(<ConnectedToolbarFilterButton />);
expect(wrapper.find('.pipeline-toolbar--filter-container').length).toBe(1);
});

it('renders filter button and divider', () => {
const props = {
displayFilterBtn: true,
onTogglePipelineFilter: jest.fn(),
};
const wrapper = setup.mount(<ToolbarFilterButton {...props} />);
expect(wrapper.find('IconButton').length).toBe(1);
expect(wrapper.find('hr.pipeline-toolbar--divider').length).toBe(1);
});

it('does not render filter button when displayFilterBtn is false', () => {
const props = {
displayFilterBtn: false,
onTogglePipelineFilter: jest.fn(),
};
const wrapper = setup.mount(<ToolbarFilterButton {...props} />);
// The container will render but the button should not be visible
expect(wrapper.find('IconButton').prop('visible')).toBe(false);
});

it('calls onTogglePipelineFilter function on button click', () => {
const mockFn = jest.fn();
const props = {
displayFilterBtn: true,
onTogglePipelineFilter: mockFn,
};
const wrapper = setup.mount(<ToolbarFilterButton {...props} />);
expect(mockFn.mock.calls.length).toBe(0);
wrapper.find('button').simulate('click');
expect(mockFn.mock.calls.length).toBe(1);
});

it('maps state to props', () => {
const mapStateToProps = (state) => ({
displayFilterBtn: state.display.filterBtn,
});

const mockDisplayState = {
display: {
filterBtn: true,
},
};

const expectedResult = {
displayFilterBtn: true,
};

expect(mapStateToProps(mockDisplayState)).toEqual(expectedResult);
});

describe('mapDispatchToProps', () => {
it('onTogglePipelineFilter', () => {
const dispatch = jest.fn();
mapDispatchToProps(dispatch).onTogglePipelineFilter();
expect(dispatch.mock.calls[0][0]).toEqual(togglePipelineFilter());
});
});
});