[UI] Getting started page for hosted pipelines (#2935)
* Implement getting started page. * Add feature flag to only show getting started page on hosted pipelines * Add tests * Fix format * Implement requested layout in getting started page * Minor adjust layout * Fix tests * Fix snapshots * Update page title
This commit is contained in:
parent
c83aff2738
commit
4709c6b42f
|
|
@ -24,7 +24,6 @@ import { Storage as GCSStorage } from '@google-cloud/storage';
|
|||
import { UIServer } from './app';
|
||||
import { loadConfigs } from './configs';
|
||||
import * as minioHelper from './minio-helper';
|
||||
import { getTensorboardInstance } from './k8s-helper';
|
||||
|
||||
jest.mock('minio');
|
||||
jest.mock('node-fetch');
|
||||
|
|
@ -46,15 +45,6 @@ describe('UIServer apis', () => {
|
|||
</script>
|
||||
<script id="kubeflow-client-placeholder"></script>
|
||||
</head>
|
||||
</html>`;
|
||||
const expectedIndexHtml = `
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
window.KFP_FLAGS.DEPLOYMENT="KUBEFLOW"
|
||||
</script>
|
||||
<script id="kubeflow-client-placeholder" src="/dashboard_lib.bundle.js"></script>
|
||||
</head>
|
||||
</html>`;
|
||||
|
||||
beforeAll(() => {
|
||||
|
|
@ -92,6 +82,15 @@ describe('UIServer apis', () => {
|
|||
});
|
||||
|
||||
it('responds with a modified index.html if it is a kubeflow deployment', done => {
|
||||
const expectedIndexHtml = `
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
window.KFP_FLAGS.DEPLOYMENT="KUBEFLOW"
|
||||
</script>
|
||||
<script id="kubeflow-client-placeholder" src="/dashboard_lib.bundle.js"></script>
|
||||
</head>
|
||||
</html>`;
|
||||
const configs = loadConfigs(argv, { DEPLOYMENT: 'kubeflow' });
|
||||
app = new UIServer(configs);
|
||||
|
||||
|
|
@ -101,6 +100,26 @@ describe('UIServer apis', () => {
|
|||
.expect('Content-Type', 'text/html; charset=utf-8')
|
||||
.expect(200, expectedIndexHtml, done);
|
||||
});
|
||||
|
||||
it('responds with flag DEPLOYMENT=MARKETPLACE if it is a marketplace deployment', done => {
|
||||
const expectedIndexHtml = `
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
window.KFP_FLAGS.DEPLOYMENT="MARKETPLACE"
|
||||
</script>
|
||||
<script id="kubeflow-client-placeholder"></script>
|
||||
</head>
|
||||
</html>`;
|
||||
const configs = loadConfigs(argv, { DEPLOYMENT: 'marketplace' });
|
||||
app = new UIServer(configs);
|
||||
|
||||
const request = requests(app.start());
|
||||
request
|
||||
.get('/')
|
||||
.expect('Content-Type', 'text/html; charset=utf-8')
|
||||
.expect(200, expectedIndexHtml, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('/apis/v1beta1/healthz', () => {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export const apiVersionPrefix = `apis/${apiVersion}`;
|
|||
export enum Deployments {
|
||||
NOT_SPECIFIED = 'NOT_SPECIFIED',
|
||||
KUBEFLOW = 'KUBEFLOW',
|
||||
MARKETPLACE = 'MARKETPLACE',
|
||||
}
|
||||
|
||||
/** converts string to bool */
|
||||
|
|
@ -132,8 +133,10 @@ export function loadConfigs(
|
|||
apiVersionPrefix,
|
||||
basePath: BASEPATH,
|
||||
deployment:
|
||||
DEPLOYMENT_STR.toUpperCase() === 'KUBEFLOW'
|
||||
DEPLOYMENT_STR.toUpperCase() === Deployments.KUBEFLOW
|
||||
? Deployments.KUBEFLOW
|
||||
: DEPLOYMENT_STR.toUpperCase() === Deployments.MARKETPLACE
|
||||
? Deployments.MARKETPLACE
|
||||
: Deployments.NOT_SPECIFIED,
|
||||
port,
|
||||
staticDir,
|
||||
|
|
|
|||
|
|
@ -66,5 +66,8 @@ function replaceRuntimeContent(content: string | undefined, deployment: Deployme
|
|||
`<script id="kubeflow-client-placeholder" src="/dashboard_lib.bundle.js"></script>`,
|
||||
);
|
||||
}
|
||||
if (content && deployment === Deployments.MARKETPLACE) {
|
||||
return content.replace(DEFAULT_FLAG, 'window.KFP_FLAGS.DEPLOYMENT="MARKETPLACE"');
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,4 +16,4 @@ const css = stylesheet({
|
|||
|
||||
export const ExternalLink: React.FC<
|
||||
DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>
|
||||
> = props => <a {...props} className={css.link} target='_blank' rel='noreferrer noopener' />;
|
||||
> = props => <a {...props} className={css.link} target='_blank' rel='noopener' />;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ import { Route, Switch, Redirect } from 'react-router-dom';
|
|||
import { classes, stylesheet } from 'typestyle';
|
||||
import { commonCss } from '../Css';
|
||||
import NewPipelineVersion from '../pages/NewPipelineVersion';
|
||||
import { GettingStarted } from '../pages/GettingStarted';
|
||||
import { KFP_FLAGS, Deployments } from '../lib/Flags';
|
||||
|
||||
export type RouteConfig = { path: string; Component: React.ComponentType<any>; view?: any };
|
||||
|
||||
|
|
@ -102,6 +104,7 @@ export const RoutePage = {
|
|||
RECURRING_RUN: `/recurringrun/details/:${RouteParams.runId}`,
|
||||
RUNS: '/runs',
|
||||
RUN_DETAILS: `/runs/details/:${RouteParams.runId}`,
|
||||
START: '/start',
|
||||
};
|
||||
|
||||
export const RoutePageFactory = {
|
||||
|
|
@ -139,9 +142,13 @@ export interface RouterProps {
|
|||
configs?: RouteConfig[]; // only used in tests
|
||||
}
|
||||
|
||||
const DEFAULT_ROUTE =
|
||||
KFP_FLAGS.DEPLOYMENT === Deployments.MARKETPLACE ? RoutePage.START : RoutePage.PIPELINES;
|
||||
|
||||
// This component is made as a wrapper to separate toolbar state for different pages.
|
||||
const Router: React.FC<RouterProps> = ({ configs }) => {
|
||||
const routes: RouteConfig[] = configs || [
|
||||
{ path: RoutePage.START, Component: GettingStarted },
|
||||
{ path: RoutePage.ARCHIVE, Component: Archive },
|
||||
{ path: RoutePage.ARTIFACTS, Component: ArtifactList },
|
||||
{ path: RoutePage.ARTIFACT_DETAILS, Component: ArtifactDetails },
|
||||
|
|
@ -170,7 +177,7 @@ const Router: React.FC<RouterProps> = ({ configs }) => {
|
|||
<Route
|
||||
exact={true}
|
||||
path={'/'}
|
||||
render={({ ...props }) => <Redirect to={RoutePage.PIPELINES} {...props} />}
|
||||
render={({ ...props }) => <Redirect to={DEFAULT_ROUTE} {...props} />}
|
||||
/>
|
||||
|
||||
{/* Normal routes */}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import { RouterProps } from 'react-router';
|
|||
import { classes, stylesheet } from 'typestyle';
|
||||
import { fontsize, commonCss } from '../Css';
|
||||
import { logger } from '../lib/Utils';
|
||||
import { KFP_FLAGS, Deployments } from '../lib/Flags';
|
||||
|
||||
export const sideNavColors = {
|
||||
bg: '#f8fafb',
|
||||
|
|
@ -267,6 +268,39 @@ export default class SideNav extends React.Component<SideNavProps, SideNavState>
|
|||
)}
|
||||
>
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
{KFP_FLAGS.DEPLOYMENT === Deployments.MARKETPLACE && (
|
||||
<>
|
||||
<div
|
||||
className={classes(
|
||||
css.indicator,
|
||||
!page.startsWith(RoutePage.START) && css.indicatorHidden,
|
||||
)}
|
||||
/>
|
||||
<Tooltip
|
||||
title={'Getting Started'}
|
||||
enterDelay={300}
|
||||
placement={'right-start'}
|
||||
disableFocusListener={!collapsed}
|
||||
disableHoverListener={!collapsed}
|
||||
disableTouchListener={!collapsed}
|
||||
>
|
||||
<Link id='gettingStartedBtn' to={RoutePage.START} className={commonCss.unstyled}>
|
||||
<Button
|
||||
className={classes(
|
||||
css.button,
|
||||
page.startsWith(RoutePage.START) && css.active,
|
||||
collapsed && css.collapsedButton,
|
||||
)}
|
||||
>
|
||||
<DescriptionIcon />
|
||||
<span className={classes(collapsed && css.collapsedLabel, css.label)}>
|
||||
Getting Started
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
className={classes(
|
||||
css.indicator,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ exports[`Description When in inline mode renders markdown link 1`] = `
|
|||
<a
|
||||
class="link"
|
||||
href="https://www.google.com"
|
||||
rel="noreferrer noopener"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
google
|
||||
|
|
@ -39,7 +39,7 @@ exports[`Description When in inline mode renders raw link 1`] = `
|
|||
<a
|
||||
class="link"
|
||||
href="https://www.google.com"
|
||||
rel="noreferrer noopener"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
https://www.google.com
|
||||
|
|
@ -52,7 +52,7 @@ exports[`Description When in normal mode renders markdown link 1`] = `
|
|||
<a
|
||||
class="link"
|
||||
href="https://www.google.com"
|
||||
rel="noreferrer noopener"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
google
|
||||
|
|
@ -91,7 +91,7 @@ exports[`Description When in normal mode renders raw link 1`] = `
|
|||
<a
|
||||
class="link"
|
||||
href="https://www.google.com"
|
||||
rel="noreferrer noopener"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
https://www.google.com
|
||||
|
|
|
|||
|
|
@ -10,102 +10,108 @@ exports[`Router initial render 1`] = `
|
|||
<Route
|
||||
exact={true}
|
||||
key="0"
|
||||
path="/archive"
|
||||
path="/start"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="1"
|
||||
path="/artifacts"
|
||||
path="/archive"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="2"
|
||||
path="/artifact_types/:artifactType+/artifacts/:id"
|
||||
path="/artifacts"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="3"
|
||||
path="/executions"
|
||||
path="/artifact_types/:artifactType+/artifacts/:id"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="4"
|
||||
path="/execution_types/:executionType+/executions/:id"
|
||||
path="/executions"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="5"
|
||||
path="/experiments"
|
||||
path="/execution_types/:executionType+/executions/:id"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="6"
|
||||
path="/experiments/details/:eid"
|
||||
path="/experiments"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="7"
|
||||
path="/experiments/new"
|
||||
path="/experiments/details/:eid"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="8"
|
||||
path="/pipeline_versions/new"
|
||||
path="/experiments/new"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="9"
|
||||
path="/runs/new"
|
||||
path="/pipeline_versions/new"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="10"
|
||||
path="/pipelines"
|
||||
path="/runs/new"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="11"
|
||||
path="/pipelines/details/:pid/version/:vid?"
|
||||
path="/pipelines"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="12"
|
||||
path="/pipelines/details/:pid?"
|
||||
path="/pipelines/details/:pid/version/:vid?"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="13"
|
||||
path="/runs"
|
||||
path="/pipelines/details/:pid?"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="14"
|
||||
path="/recurringrun/details/:rid"
|
||||
path="/runs"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="15"
|
||||
path="/runs/details/:rid"
|
||||
path="/recurringrun/details/:rid"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="16"
|
||||
path="/runs/details/:rid"
|
||||
render={[Function]}
|
||||
/>
|
||||
<Route
|
||||
exact={true}
|
||||
key="17"
|
||||
path="/compare"
|
||||
render={[Function]}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,18 @@
|
|||
export enum Deployments {
|
||||
KUBEFLOW = 'KUBEFLOW',
|
||||
MARKETPLACE = 'MARKETPLACE',
|
||||
}
|
||||
|
||||
export const KFP_FLAGS = {
|
||||
DEPLOYMENT:
|
||||
// tslint:disable-next-line:no-string-literal
|
||||
window && window['KFP_FLAGS'] && window['KFP_FLAGS']['DEPLOYMENT'] === Deployments.KUBEFLOW
|
||||
? Deployments.KUBEFLOW
|
||||
window && window['KFP_FLAGS']
|
||||
? // tslint:disable-next-line:no-string-literal
|
||||
window['KFP_FLAGS']['DEPLOYMENT'] === Deployments.KUBEFLOW
|
||||
? Deployments.KUBEFLOW
|
||||
: // tslint:disable-next-line:no-string-literal
|
||||
window['KFP_FLAGS']['DEPLOYMENT'] === Deployments.MARKETPLACE
|
||||
? Deployments.MARKETPLACE
|
||||
: undefined
|
||||
: undefined,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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 Buttons from '../lib/Buttons';
|
||||
import { Page } from './Page';
|
||||
import { ToolbarProps } from '../components/Toolbar';
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
import { ExternalLink } from '../atoms/ExternalLink';
|
||||
import { cssRaw, classes } from 'typestyle';
|
||||
import { commonCss, padding } from '../Css';
|
||||
|
||||
const options = {
|
||||
overrides: { a: { component: ExternalLink } },
|
||||
};
|
||||
|
||||
const PAGE_CONTENT_MD = `
|
||||
## Build your own pipeline
|
||||
|
||||
Build a end-to-end ML pipeline with TFX [Start Here](https://console.cloud.google.com/mlengine/notebooks/deploy-notebook?q=download_url%3Dhttps%253A%252F%252Fraw.githubusercontent.com%252Fkubeflow%252Fpipelines%252F0.1.40%252Fsamples%252Fcore%252Fparameterized_tfx_oss%252Ftaxi_pipeline_notebook.ipynb) (Alpha)
|
||||
|
||||
## Demos and Tutorials
|
||||
|
||||
This section contains demo and tutorial pipelines.
|
||||
|
||||
**Demos** - Try an end-to-end demonstration pipeline.
|
||||
|
||||
* [TFX pipeline demo](https://www.google.com) \\- A trainer that does end-to-end distributed training for XGBoost models. [source code](https://github.com/kubeflow/pipelines/tree/master/samples/core/parameterized_tfx_oss)
|
||||
* [XGBoost Pipeline](https://www.google.com) \\- Example pipeline that does classification with model analysis based on a public taxi cab BigQuery dataset. [source code](https://github.com/kubeflow/pipelines/tree/master/samples/core/xgboost_training_cm)
|
||||
|
||||
|
||||
**Tutorials** - Learn pipeline concepts by following a tutorial.
|
||||
|
||||
* [Name of Tutorial 1] - \\<tutorial 1 description\\>. [source code]()
|
||||
* [Name of Tutorial 2] - \\<tutorial 2 description\\>. [source code]()
|
||||
|
||||
You can find additional tutorials and samples [here]()
|
||||
|
||||
### Additional resources and documentation
|
||||
* [TFX Landing page]()
|
||||
* [Hosted Pipeline documentation]()
|
||||
* [Troubleshooting guide]()
|
||||
`;
|
||||
|
||||
cssRaw(`
|
||||
.kfp-start-page li {
|
||||
font-size: 14px;
|
||||
margin-block-start: 0.83em;
|
||||
margin-block-end: 0.83em;
|
||||
margin-left: 2em;
|
||||
}
|
||||
.kfp-start-page p {
|
||||
font-size: 14px;
|
||||
margin-block-start: 0.83em;
|
||||
margin-block-end: 0.83em;
|
||||
}
|
||||
.kfp-start-page h2 {
|
||||
font-size: 18px;
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 1em;
|
||||
}
|
||||
.kfp-start-page h3 {
|
||||
font-size: 16px;
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 1em;
|
||||
}
|
||||
`);
|
||||
|
||||
export class GettingStarted extends Page<{}, {}> {
|
||||
public getInitialToolbarState(): ToolbarProps {
|
||||
const buttons = new Buttons(this.props, this.refresh.bind(this));
|
||||
return {
|
||||
actions: buttons.getToolbarActionMap(),
|
||||
breadcrumbs: [],
|
||||
pageTitle: 'Getting Started',
|
||||
};
|
||||
}
|
||||
|
||||
public async refresh() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className={classes(commonCss.page, padding(20, 'lr'), 'kfp-start-page')}>
|
||||
<Markdown options={options}>{PAGE_CONTENT_MD}</Markdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -557,6 +557,8 @@ spec:
|
|||
value: {{ .Release.Namespace }}
|
||||
- name: ALLOW_CUSTOM_VISUALIZATIONS
|
||||
value: "true"
|
||||
- name: DEPLOYMENT
|
||||
value: MARKETPLACE
|
||||
image: {{ .Values.images.frontend }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: ml-pipeline-ui
|
||||
|
|
|
|||
Loading…
Reference in New Issue