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

Adds Metadata Artifacts to UI #2057

Merged
merged 12 commits into from
Sep 6, 2019
Prev Previous commit
Next Next commit
Begin moving some metadata UI pages to KFP
  • Loading branch information
rileyjbauer committed Sep 6, 2019
commit 2d441bfe78c03bfc66a79839ef293c929658b499
6 changes: 6 additions & 0 deletions frontend/src/Css.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ export const commonCss = stylesheet({
paddingBottom: 16,
paddingTop: 20,
},
header2: {
fontSize: fontsize.medium,
fontWeight: 'bold',
paddingBottom: 16,
paddingTop: 20,
},
infoIcon: {
color: color.lowContrast,
height: 16,
Expand Down
41 changes: 18 additions & 23 deletions frontend/src/components/CustomTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ import Separator from '../atoms/Separator';
import TableSortLabel from '@material-ui/core/TableSortLabel';
import TextField, { TextFieldProps } from '@material-ui/core/TextField';
import Tooltip from '@material-ui/core/Tooltip';
import WarningIcon from '@material-ui/icons/WarningRounded';
import { ListRequest } from '../lib/Apis';
import { classes, stylesheet } from 'typestyle';
import { fonts, fontsize, dimension, commonCss, color, padding, zIndex } from '../Css';
import { logger } from '../lib/Utils';
import { ApiFilter, PredicateOp } from '../apis/filter/api';
import { debounce } from 'lodash';
import { InputAdornment } from '@material-ui/core';
import { CustomTableRow } from './CustomTableRow';

export enum ExpandState {
COLLAPSED,
Expand Down Expand Up @@ -102,6 +102,9 @@ export const css = stylesheet({
expandButtonExpanded: {
transform: 'rotate(90deg)',
},
expandButtonPlaceholder: {
width: 54,
},
expandableContainer: {
transition: 'margin 0.2s',
},
Expand Down Expand Up @@ -134,13 +137,6 @@ export const css = stylesheet({
flex: '0 0 40px',
lineHeight: '40px', // must declare px
},
icon: {
color: color.alert,
height: 18,
paddingRight: 4,
verticalAlign: 'sub',
width: 18,
},
noLeftPadding: {
paddingLeft: 0,
},
Expand Down Expand Up @@ -291,7 +287,7 @@ export default class CustomTable extends React.Component<CustomTableProps, Custo
{!this.props.noFilterBox && (
<div>
<Input id='tableFilterBox' label={this.props.filterLabel || 'Filter'} height={48} maxWidth={'100%'}
className={css.filterBox} InputLabelProps={{ classes: { root: css.noMargin }}}
className={css.filterBox} InputLabelProps={{ classes: { root: css.noMargin } }}
onChange={this.handleFilterChange} value={filterString}
variant='outlined'
InputProps={{
Expand All @@ -301,7 +297,7 @@ export default class CustomTable extends React.Component<CustomTableProps, Custo
},
startAdornment: (
<InputAdornment position='end'>
<FilterIcon style={{ color: color.lowContrast, paddingRight: 16 }}/>
<FilterIcon style={{ color: color.lowContrast, paddingRight: 16 }} />
</InputAdornment>
)
}} />
Expand Down Expand Up @@ -373,7 +369,8 @@ export default class CustomTable extends React.Component<CustomTableProps, Custo
row.expandState === ExpandState.EXPANDED && css.expandedRow
)}
onClick={e => this.handleClick(e, row.id)}>
{(this.props.disableSelection !== true || !!this.props.getExpandComponent) && (
{/* Expansion toggle button */}
{((this.props.disableSelection !== true || !!this.props.getExpandComponent) && row.expandState !== ExpandState.NONE) && (
<div className={classes(css.cell, css.selectionToggle)}>
{/* If using checkboxes */}
{(this.props.disableSelection !== true && this.props.useRadioButtons !== true) && (
Expand All @@ -390,18 +387,16 @@ export default class CustomTable extends React.Component<CustomTableProps, Custo
)}
</div>
)}
{row.otherFields.map((cell, c) => (
<div key={c} style={{ width: widths[c] + '%' }} className={css.cell}>
{c === 0 && row.error && (
<Tooltip title={row.error}><WarningIcon className={css.icon} /></Tooltip>
)}
{this.props.columns[c].customRenderer ?
this.props.columns[c].customRenderer!({ value: cell, id: row.id }) : cell}
</div>
))}

{/* Placeholder for non-expandable rows */}
{row.expandState === ExpandState.NONE && (
<div className={css.expandButtonPlaceholder} />
)}

{<CustomTableRow row={row} columns={this.props.columns} />}
</div>
{row.expandState === ExpandState.EXPANDED && this.props.getExpandComponent && (
<div className={padding(20, 'lrb')}>
<div>
{this.props.getExpandComponent(i)}
</div>
)}
Expand Down Expand Up @@ -506,8 +501,8 @@ export default class CustomTable extends React.Component<CustomTableProps, Custo
// invert the sort order it if it's the same column
const sortOrder =
this.state.sortBy === sortBy
? (this.state.sortOrder === 'asc' ? 'desc' : 'asc')
: 'asc';
? (this.state.sortOrder === 'asc' ? 'desc' : 'asc')
: 'asc';
this.setStateSafe({ sortOrder, sortBy }, async () => {
this._resetToFirstPage(
await this.reload({ pageToken: '', orderAscending: sortOrder === 'asc', sortBy }));
Expand Down
92 changes: 92 additions & 0 deletions frontend/src/components/CustomTableRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as React from 'react';
import Tooltip from '@material-ui/core/Tooltip';
import WarningIcon from '@material-ui/icons/WarningRounded';
import { Row, Column } from './CustomTable';
import { color, fonts, fontsize } from '../Css';
import { stylesheet } from 'typestyle';

export const css = stylesheet({
cell: {
$nest: {
'&:not(:nth-child(2))': {
color: color.inactive,
},
},
alignSelf: 'center',
borderBottom: 'initial',
color: color.foreground,
fontFamily: fonts.secondary,
fontSize: fontsize.base,
letterSpacing: 0.25,
marginRight: 20,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
},
icon: {
color: color.alert,
height: 18,
paddingRight: 4,
verticalAlign: 'sub',
width: 18,
},
row: {
$nest: {
'&:hover': {
backgroundColor: '#f3f3f3',
},
},
borderBottom: '1px solid #ddd',
display: 'flex',
flexShrink: 0,
height: 40,
outline: 'none',
},
});

interface CustomTableRowProps {
row: Row;
columns: Column[];
}

function calculateColumnWidths(columns: Column[]): number[] {
const totalFlex = columns.reduce((total, c) => total += (c.flex || 1), 0);
return columns.map(c => (c.flex || 1) / totalFlex * 100);
}

// tslint:disable-next-line:variable-name
export const CustomTableRow: React.FC<CustomTableRowProps> = (props: CustomTableRowProps) => {
const { row, columns } = props;
const widths = calculateColumnWidths(columns);
return (
<React.Fragment>
{
row.otherFields.map((cell, i) => (
<div key={i} style={{ width: widths[i] + '%' }} className={css.cell}>
{i === 0 && row.error && (
<Tooltip title={row.error}><WarningIcon className={css.icon} /></Tooltip>
)}
{columns[i].customRenderer ?
columns[i].customRenderer!({ value: cell, id: row.id }) : cell}
</div>
))
}
</React.Fragment>
);
};
87 changes: 87 additions & 0 deletions frontend/src/components/ResourceInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import { stylesheet } from 'typestyle';
import { color, commonCss } from '../Css';
import { getMetadataValue } from '../lib/Utils';
import { Artifact, Execution } from '../generated/src/apis/metadata/metadata_store_pb';

export const css = stylesheet({
field: {
flexBasis: '300px',
marginBottom: '32px',
},
resourceInfo: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
},
term: {
color: color.grey,
fontSize: '12px',
letterSpacing: '0.2px',
lineHeight: '16px',
},
value: {
color: color.secondaryText,
fontSize: '14px',
letterSpacing: '0.2px',
lineHeight: '20px',
}
});

export interface ResourceInfoProps {
resource: Artifact | Execution;
typeName: string;
}

export class ResourceInfo extends React.Component<ResourceInfoProps, {}> {

public render(): JSX.Element {
const { resource } = this.props;
const propertyMap = resource.getPropertiesMap();
const customPropertyMap = resource.getCustomPropertiesMap();
return (
<section>
<h1 className={commonCss.header}>Type: {this.props.typeName}</h1>
<h2 className={commonCss.header2}>Properties</h2>
<dl className={css.resourceInfo}>
{Object.keys(propertyMap || {})
// TODO: __ALL_META__ is something of a hack, is redundant, and can be ignored
.filter(k => k !== '__ALL_META__')
.map(k =>
<div className={css.field} key={k}>
<dt className={css.term}>{k}</dt>
<dd className={css.value}>{propertyMap && getMetadataValue(propertyMap.get(k))}</dd>
</div>
)
}
</dl>
<h2 className={commonCss.header2}>Custom Properties</h2>
<dl className={css.resourceInfo}>
{Object.keys(customPropertyMap || {}).map(k =>
<div className={css.field} key={k}>
<dt className={css.term}>{k}</dt>
<dd className={css.value}>
{customPropertyMap && getMetadataValue(customPropertyMap.get(k))}
</dd>
</div>
)}
</dl>
</section>
);
}
}
12 changes: 12 additions & 0 deletions frontend/src/components/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import * as React from 'react';
import Archive from '../pages/Archive';
import ArtifactList from '../pages/ArtifactList';
import ArtifactDetails from '../pages/ArtifactDetails';
import Banner, { BannerProps } from '../components/Banner';
import Button from '@material-ui/core/Button';
import Compare from '../pages/Compare';
Expand Down Expand Up @@ -61,12 +63,20 @@ export enum RouteParams {
experimentId = 'eid',
pipelineId = 'pid',
runId = 'rid',
ARTIFACT_TYPE = 'artifactType',
EXECUTION_TYPE = 'executionType',
// TODO: create one of these for artifact and execution?
ID = 'id',
}

// tslint:disable-next-line:variable-name
export const RoutePage = {
ARCHIVE: '/archive',
ARTIFACTS: '/artifacts',
ARTIFACT_DETAILS: `/artifact_types/:${RouteParams.ARTIFACT_TYPE}+/artifacts/:${RouteParams.ID}`,
COMPARE: `/compare`,
EXECUTIONS: '/executions',
EXECUTION_DETAILS: `/execution_types/:${RouteParams.EXECUTION_TYPE}+/executions/:${RouteParams.ID}`,
EXPERIMENTS: '/experiments',
EXPERIMENT_DETAILS: `/experiments/details/:${RouteParams.experimentId}`,
NEW_EXPERIMENT: '/experiments/new',
Expand Down Expand Up @@ -118,6 +128,8 @@ class Router extends React.Component<{}, RouteComponentState> {

const routes: Array<{ path: string, Component: React.ComponentClass, view?: any }> = [
{ path: RoutePage.ARCHIVE, Component: Archive },
{ path: RoutePage.ARTIFACTS, Component: ArtifactList },
{ path: RoutePage.ARTIFACT_DETAILS, Component: ArtifactDetails },
{ path: RoutePage.EXPERIMENTS, Component: ExperimentsAndRuns, view: ExperimentsAndRunsTab.EXPERIMENTS },
{ path: RoutePage.EXPERIMENT_DETAILS, Component: ExperimentDetails },
{ path: RoutePage.NEW_EXPERIMENT, Component: NewExperiment },
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/components/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import * as React from 'react';
import ArchiveIcon from '@material-ui/icons/Archive';
import ArtifactsIcon from '@material-ui/icons/BubbleChart';
import Button from '@material-ui/core/Button';
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
import ExperimentsIcon from '../icons/experiments';
Expand Down Expand Up @@ -251,6 +252,19 @@ export default class SideNav extends React.Component<SideNavProps, SideNavState>
</Button>
</Link>
</Tooltip>
<div className={classes(css.indicator, !page.startsWith(RoutePage.ARTIFACTS) && css.indicatorHidden)} />
<Tooltip title={'Artifacts List'} enterDelay={300} placement={'right-start'}
disableFocusListener={!collapsed} disableHoverListener={!collapsed}
disableTouchListener={!collapsed}>
<Link id='artifactsBtn' to={RoutePage.ARTIFACTS} className={commonCss.unstyled}>
<Button className={classes(css.button,
this._highlightArtifactsButton(page) && css.active,
collapsed && css.collapsedButton)}>
<ArtifactsIcon />
<span className={classes(collapsed && css.collapsedLabel, css.label)}>Artifacts</span>
</Button>
</Link>
</Tooltip>
{this.state.jupyterHubAvailable && (
<Tooltip title={'Open Jupyter Notebook'} enterDelay={300} placement={'right-start'}
disableFocusListener={!collapsed} disableHoverListener={!collapsed}
Expand Down Expand Up @@ -311,6 +325,11 @@ export default class SideNav extends React.Component<SideNavProps, SideNavState>
|| page.startsWith(RoutePage.COMPARE);
}

private _highlightArtifactsButton(page: string): boolean {
// TODO: Router should have a constant for this, but it doesn't follow a naming convention
return page.startsWith('/artifact');
}

private _toggleNavClicked(): void {
this.setStateSafe({
collapsed: !this.state.collapsed,
Expand Down
Loading