Skip to content

Commit

Permalink
[KED-1423] Feature/metadata panel (#276)
Browse files Browse the repository at this point in the history
* [KED-1423] Add metadata panel

* [KED-1423] Added modifiers utility

* [KED-1423] Refactor metadata panel components

* [KED-1423] Added tests for modifiers utility

* [KED-1423] Improved metadata panel

* [KED-1423] Add tests for metadata panel

* [KED-1887] Add option for additional classname string on modifiers

* [KED-1887] Add classname for icon type to NodeIcon

* [KED-1423] Add tests for metadata panel

* [KED-1423] Show tag names on metadata panel

* [KED-1423] Hide inputs and outputs for datasets on metadata panel

* [KED-1423] Remove token style from type and run command on metadata panel

* [KED-1423] Refactor run commands on metadata panel

* [KED-1423] Refactor MetaDataValue

* [KED-1423] Use colour constants on metadata text

* [KED-1423] Fix run command on metadata panel

* [KED-1423] Update typography and fix icon classname

* [KED-1423] Only show inputs and outputs for task type on metadata panel

* [KED-1423] Fix icon button classnames

* [KED-1423] Update typography on metadata panel

* [KED-1423] Align copy button on metadata panel

* [KED-1423] Simplify metadata panel

* [KED-1423] Simplify metadata panel

* [KED-1423] Refactor metadata panel

* [KED-1423] Simplify metadata selector

* [KED-1423] Reduce copy message timeout on metadata panel

* [KED-1423] Refactor CSS on metadata panel

* [KED-1423] Revert naming on layout selectors

* [KED-1423] Rename getNodesById and duplicate getClickedNode

* [KED-1423] Fix spacing after refactor CSS on metadata panel

* [KED-1423] Refactor MetaDataRow values on metadata panel

* [KED-1423] Refactor MetaDataRow to use `dt` and `dd` elements

* [KED-1423] Add close button to metadata panel

* [KED-1423] Update metadata panel colours

* [KED-1423] Add comment on double click zoom disabled

* [KED-1423] Improve responsive approach for sidebars and zoom

* [KED-1423] Change copy message as overlay on metadata panel

* [KED-1423] Convert text colors to opacity on metadata panel

* [KED-1423] Change toolbox container element and translate function on metadata panel

* [KED-1423] Change responsive approach on metadata panel

* [KED-1423] Fix empty value for lists on metadata panel

* Improve sidebar width handling on smaller screens

* Revert sidebar full-width breakpoint changes

* Remove sidebar min-widths

* [KED-1423] Move run commands out of config

* [KED-1423] Fix layout selector tests

* [KED-1423] Fix metadata panel tests

* [KED-1423] Style tweaks on metadata panel

* [KED-1423] Add metadata panel flag

* [KED-1423] Fix metadata panel tests

Co-authored-by: Richard Westenra <[email protected]>
  • Loading branch information
bru5 and richardwestenra authored Oct 30, 2020
1 parent 35e4872 commit 5a7096b
Show file tree
Hide file tree
Showing 23 changed files with 830 additions and 45 deletions.
9 changes: 6 additions & 3 deletions src/components/flowchart/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export class FlowChart extends Component {
minScale = 0,
maxScale = Infinity
] = this.zoomBehaviour.scaleExtent();
const { sidebarWidth } = this.props.chartSize;
const { sidebarWidth, metaSidebarWidth } = this.props.chartSize;
const { width = 0, height = 0 } = this.props.graphSize;

// Limit zoom translate extent: This needs to be recalculated on zoom
Expand All @@ -213,7 +213,7 @@ export class FlowChart extends Component {
const margin = 500;
this.zoomBehaviour.translateExtent([
[-sidebarWidth / scale - margin, -margin],
[width + margin, height + margin]
[width + margin + metaSidebarWidth / scale, height + margin]
]);

// Transform the <g> that wraps the chart
Expand Down Expand Up @@ -255,7 +255,10 @@ export class FlowChart extends Component {
);
});

this.el.svg.call(this.zoomBehaviour);
this.el.svg
.call(this.zoomBehaviour)
// Disabled to avoid conflicts with metadata panel triggered zooms
.on('dblclick.zoom', null);
}

/**
Expand Down
13 changes: 7 additions & 6 deletions src/components/icon-button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import './icon-button.css';

/**
* Icon button component
* @param {Function} onToggle Handle toggling theme between light/dark
* @param {string} theme Kedro UI light/dark theme
*/
const IconButton = ({
container: Container = 'li',
ariaLabel,
ariaLive,
className,
Expand All @@ -22,7 +21,7 @@ const IconButton = ({
const Icon = icon;

return visible ? (
<li>
<Container>
<button
aria-label={ariaLabel}
aria-live={ariaLive}
Expand All @@ -32,10 +31,12 @@ const IconButton = ({
})}
disabled={disabled}
onClick={onClick}>
<span className="pipeline-toolbar__label">{labelText}</span>
{Icon && <Icon className={'pipeline-icon'} />}
{Icon && <Icon className="pipeline-icon" />}
{labelText && (
<span className="pipeline-toolbar__label">{labelText}</span>
)}
</button>
</li>
</Container>
) : null;
};

Expand Down
10 changes: 10 additions & 0 deletions src/components/icons/close.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';

export default ({ className }) => (
<svg
className={className}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
</svg>
);
10 changes: 10 additions & 0 deletions src/components/icons/copy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';

export default ({ className }) => (
<svg
className={className}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<path d="M17 1v1h4v16h-4v4H3V6h4V2h4V1h6zM7 8H5v12h10v-2H7V8zm4-4H9v12h10V4h-2v1h-6V4z" />
</svg>
);
5 changes: 4 additions & 1 deletion src/components/icons/node-icon.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import modifiers from '../../utils/modifiers';

export const paths = {
// database icon
Expand All @@ -14,7 +15,9 @@ export const paths = {

export default ({ className, type }) =>
paths[type] ? (
<svg className={className} viewBox="0 0 24 24">
<svg
className={modifiers('pipeline-node-icon', { type }, className)}
viewBox="0 0 24 24">
<path d={paths[type]} />
</svg>
) : null;
132 changes: 132 additions & 0 deletions src/components/metadata/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import React, { useState } from 'react';
import { connect } from 'react-redux';
import modifiers from '../../utils/modifiers';
import NodeIcon from '../../components/icons/node-icon';
import IconButton from '../../components/icon-button';
import CopyIcon from '../icons/copy';
import CloseIcon from '../icons/close';
import MetaDataRow from './metadata-row';
import MetaDataValue from './metadata-value';
import {
getVisibleMetaSidebar,
getClickedNodeMetaData
} from '../../selectors/metadata';
import { toggleNodeClicked } from '../../actions/nodes';
import './styles/metadata.css';

/**
* Shows node meta data
*/
const MetaData = ({ visible = true, metadata, onToggleNodeSelected }) => {
const [showCopied, setShowCopied] = useState(false);

const isTaskNode = metadata?.node.type === 'task';

const onCopyClick = () => {
window.navigator.clipboard.writeText(metadata.runCommand);
setShowCopied(true);
setTimeout(() => setShowCopied(false), 1500);
};

const onCloseClick = () => {
// Deselecting a node automatically hides MetaData panel
onToggleNodeSelected(null);
};

if (!metadata) {
return <div className="pipeline-metadata kedro" />;
}

return (
<div className={modifiers('pipeline-metadata', { visible }, 'kedro')}>
<div className="pipeline-metadata__header">
<NodeIcon
className="pipeline-metadata__icon"
type={metadata.node.type}
/>
<h2 className="pipeline-metadata__title">{metadata.node.name}</h2>
<IconButton
container={React.Fragment}
ariaLabel="Close Metadata Panel"
className="pipeline-metadata__close-button"
icon={CloseIcon}
onClick={onCloseClick}
/>
</div>
<dl className="pipeline-metadata__list">
<MetaDataRow label="Type:" value={metadata.node.type} />
<MetaDataRow
label="Inputs:"
property="name"
visible={isTaskNode}
value={metadata.inputs}
/>
<MetaDataRow
label="Outputs:"
property="name"
visible={isTaskNode}
value={metadata.outputs}
/>
<MetaDataRow
label="Tags:"
kind="token"
commas={false}
value={metadata.tags}
/>
<MetaDataRow
label="Pipeline:"
visible={Boolean(metadata.pipeline)}
value={metadata.pipeline}
/>
<MetaDataRow
label="Run Command:"
visible={Boolean(metadata.runCommand)}>
<div className="pipeline-metadata__toolbox-container">
<MetaDataValue
container={'code'}
className={modifiers('pipeline-metadata__run-command-value', {
visible: !showCopied
})}
value={metadata.runCommand}
/>
{window.navigator.clipboard && (
<>
<span
className={modifiers('pipeline-metadata__copy-message', {
visible: showCopied
})}>
Copied to clipboard.
</span>
<ul className="pipeline-metadata__toolbox">
<IconButton
ariaLabel="Copy run command to clipboard."
className="pipeline-metadata__copy-button"
icon={CopyIcon}
onClick={onCopyClick}
/>
</ul>
</>
)}
</div>
</MetaDataRow>
</dl>
</div>
);
};

export const mapStateToProps = (state, ownProps) => ({
visible: getVisibleMetaSidebar(state),
metadata: getClickedNodeMetaData(state),
...ownProps
});

export const mapDispatchToProps = dispatch => ({
onToggleNodeSelected: nodeID => {
dispatch(toggleNodeClicked(nodeID));
}
});

export default connect(
mapStateToProps,
mapDispatchToProps
)(MetaData);
37 changes: 37 additions & 0 deletions src/components/metadata/metadata-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import modifiers from '../../utils/modifiers';
import MetaDataValue from './metadata-value';
import './styles/metadata.css';

/**
* Shows a list of MetaDataValue
*/
const MetaDataList = ({
property,
values = [],
kind = 'text',
empty = '-',
inline = true,
commas = true
}) =>
values.length > 0 ? (
<ul
className={modifiers('pipeline-metadata__value-list', {
inline,
commas
})}>
{values.map((item, index) => (
<li key={index}>
<MetaDataValue
value={property ? item[property] : item}
kind={kind}
empty={empty}
/>
</li>
))}
</ul>
) : (
<MetaDataValue empty={empty} />
);

export default MetaDataList;
48 changes: 48 additions & 0 deletions src/components/metadata/metadata-row.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import MetaDataList from './metadata-list';
import MetaDataValue from './metadata-value';
import './styles/metadata.css';

/**
* Shows metadata label and a given single value, or a list of values, or child elements
*/
const MetaDataRow = ({
label,
property,
value,
kind = 'text',
empty = '-',
visible = true,
inline = true,
commas = true,
children
}) => {
const showList = Array.isArray(value);
const showValue = !showList && typeof value !== 'undefined';

return (
visible && (
<>
<dt className="pipeline-metadata__label">{label}</dt>
<dd className="pipeline-metadata__row" data-label={label}>
{showList && (
<MetaDataList
property={property}
inline={inline}
commas={commas}
kind={kind}
empty={empty}
values={value}
/>
)}
{showValue && (
<MetaDataValue value={value} kind={kind} empty={empty} />
)}
{children}
</dd>
</>
)
);
};

export default MetaDataRow;
21 changes: 21 additions & 0 deletions src/components/metadata/metadata-value.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import modifiers from '../../utils/modifiers';
import './styles/metadata.css';

/**
* Shows a metadata value
*/
const MetaDataValue = ({
container: Container = 'span',
className,
value,
kind,
empty
}) => (
<Container
className={modifiers('pipeline-metadata__value', { kind }, className)}>
{!value && value !== 0 ? empty : value}
</Container>
);

export default MetaDataValue;
Loading

0 comments on commit 5a7096b

Please sign in to comment.