-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(frontend): Integrate pipeline / pipeline version functions with…
… KFP v2 API (#9123) * Separate pipelineServiceApiV1 and pipelineServiceApiV2. Add uploadPipelineVersionV2 helper. * Ingegrate new pipeline functionality with v2 API. Create a new pipeline dialog for v2 pipeline because the error is not string type. * Integrate getPipelineVersion() in run/recurring run details router with v2 API. * Integrate listPipelines() / listPipelineVersions() in PipelineList and PipelineVersionList with v2 API. * Update private and shared pipelines unit tests. * Separate v1 and v2 logics (based on API return value) in pipeline details (router). Noted: add additional listPipelines() to get latest version because v2 don't default version field. * Change pipeline props in PipelineDetailsV2 and PipelineVersionCard Remove default version in PipelineVersionCard test. Append version id to URL for unspecified versionId scenario. * Create new ResourceSelectorV2 and make corresponding changes (back to v2beta1Pipeline) in PipelineDialogV2 and NewPipelineVersion * Create resource converter to make various API returned objects fit in base resource. Change the behavior of "selectionChanged" to achive more general usage. * Integrate getPipeline() / getPipelineVersion() in "create / clone run" with v2 API. * Integrate get started page with v2 pipeline API. * Integrate getPipelineVersion() in run list with v2 API. * Integrate delete pipeline / pipeline version with v2 API. * Remove unused imports. Change relative path to absolute path. * Add unit test for PipelineDialogV2 and ResourceSelectorV2. * Fix incorrect Version Description in PipelineVersionCard and rename the constant in unit test. Add size limitation in the listPipelineVersions() called by getSelectedVersion. Add missing props in NewRunV2 test. Fix FE-integration test (trial) * Simplify the logic of deletePipelineVersion. Remove dialoghandlerMultiId.
- Loading branch information
Showing
33 changed files
with
1,844 additions
and
896 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* | ||
* Copyright 2023 The Kubeflow Authors | ||
* | ||
* 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 { render, screen } from '@testing-library/react'; | ||
import PipelinesDialogV2, { PipelinesDialogV2Props } from './PipelinesDialogV2'; | ||
import { PageProps } from 'src/pages/Page'; | ||
import { Apis, PipelineSortKeys } from 'src/lib/Apis'; | ||
import { V2beta1Pipeline, V2beta1ListPipelinesResponse } from 'src/apisv2beta1/pipeline'; | ||
import TestUtils from 'src/TestUtils'; | ||
import { BuildInfoContext } from 'src/lib/BuildInfo'; | ||
|
||
function generateProps(): PipelinesDialogV2Props { | ||
return { | ||
...generatePageProps(), | ||
open: true, | ||
selectorDialog: '', | ||
onClose: jest.fn(), | ||
namespace: 'ns', | ||
pipelineSelectorColumns: [ | ||
{ | ||
flex: 1, | ||
label: 'Pipeline name', | ||
sortKey: PipelineSortKeys.NAME, | ||
}, | ||
{ label: 'Description', flex: 2 }, | ||
{ label: 'Uploaded on', flex: 1, sortKey: PipelineSortKeys.CREATED_AT }, | ||
], | ||
}; | ||
} | ||
|
||
function generatePageProps(): PageProps { | ||
return { | ||
history: {} as any, | ||
location: '' as any, | ||
match: {} as any, | ||
toolbarProps: {} as any, | ||
updateBanner: jest.fn(), | ||
updateDialog: jest.fn(), | ||
updateSnackbar: jest.fn(), | ||
updateToolbar: jest.fn(), | ||
}; | ||
} | ||
|
||
const oldPipeline: V2beta1Pipeline = { | ||
pipeline_id: 'old-run-pipeline-id', | ||
display_name: 'old mock pipeline name', | ||
}; | ||
|
||
const newPipeline: V2beta1Pipeline = { | ||
pipeline_id: 'new-run-pipeline-id', | ||
display_name: 'new mock pipeline name', | ||
}; | ||
|
||
describe('PipelinesDialog', () => { | ||
let listPipelineSpy: jest.SpyInstance<{}>; | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
listPipelineSpy = jest | ||
.spyOn(Apis.pipelineServiceApiV2, 'listPipelines') | ||
.mockImplementation((...args) => { | ||
const response: V2beta1ListPipelinesResponse = { | ||
pipelines: [oldPipeline, newPipeline], | ||
total_size: 2, | ||
}; | ||
return Promise.resolve(response); | ||
}); | ||
}); | ||
|
||
afterEach(async () => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
it('it renders correctly in multi user mode', async () => { | ||
const tree = render( | ||
<BuildInfoContext.Provider value={{ apiServerMultiUser: true }}> | ||
<PipelinesDialogV2 {...generateProps()} /> | ||
</BuildInfoContext.Provider>, | ||
); | ||
await TestUtils.flushPromises(); | ||
|
||
expect(listPipelineSpy).toHaveBeenCalledWith('ns', '', 10, 'created_at desc', ''); | ||
screen.getByText('old mock pipeline name'); | ||
screen.getByText('new mock pipeline name'); | ||
}); | ||
|
||
it('it renders correctly in single user mode', async () => { | ||
const tree = render( | ||
<BuildInfoContext.Provider value={{ apiServerMultiUser: false }}> | ||
<PipelinesDialogV2 {...generateProps()} /> | ||
</BuildInfoContext.Provider>, | ||
); | ||
await TestUtils.flushPromises(); | ||
|
||
expect(listPipelineSpy).toHaveBeenCalledWith(undefined, '', 10, 'created_at desc', ''); | ||
screen.getByText('old mock pipeline name'); | ||
screen.getByText('new mock pipeline name'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
/* | ||
* Copyright 2023 The Kubeflow Authors | ||
* | ||
* 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 Button from '@material-ui/core/Button'; | ||
import Dialog from '@material-ui/core/Dialog'; | ||
import DialogActions from '@material-ui/core/DialogActions'; | ||
import { classes } from 'typestyle'; | ||
import { padding, commonCss } from 'src/Css'; | ||
import DialogContent from '@material-ui/core/DialogContent'; | ||
import ResourceSelectorV2 from 'src/pages/ResourceSelectorV2'; | ||
import { Apis, PipelineSortKeys } from 'src/lib/Apis'; | ||
import { Column } from './CustomTable'; | ||
import { V2beta1Pipeline } from 'src/apisv2beta1/pipeline'; | ||
import Buttons from 'src/lib/Buttons'; | ||
import { PageProps } from 'src/pages/Page'; | ||
import MD2Tabs from 'src/atoms/MD2Tabs'; | ||
import Toolbar, { ToolbarActionMap } from 'src/components/Toolbar'; | ||
import { PipelineTabsHeaders, PipelineTabsTooltips } from 'src/pages/PrivateAndSharedPipelines'; | ||
import { BuildInfoContext } from 'src/lib/BuildInfo'; | ||
import { convertPipelineToResource } from 'src/lib/ResourceConverter'; | ||
|
||
enum NamespacedAndSharedTab { | ||
NAMESPACED = 0, | ||
SHARED = 1, | ||
} | ||
|
||
export interface PipelinesDialogV2Props extends PageProps { | ||
open: boolean; | ||
selectorDialog: string; | ||
onClose: (confirmed: boolean, selectedPipeline?: V2beta1Pipeline) => void; | ||
namespace: string | undefined; // use context or make it optional? | ||
pipelineSelectorColumns: Column[]; | ||
toolbarActionMap?: ToolbarActionMap; | ||
} | ||
|
||
const PipelinesDialogV2: React.FC<PipelinesDialogV2Props> = (props): JSX.Element | null => { | ||
const buildInfo = React.useContext(BuildInfoContext); | ||
const [view, setView] = React.useState(NamespacedAndSharedTab.NAMESPACED); | ||
const [unconfirmedSelectedPipeline, setUnconfirmedSelectedPipeline] = React.useState< | ||
V2beta1Pipeline | ||
>(); | ||
|
||
function getPipelinesList(): JSX.Element { | ||
return ( | ||
<ResourceSelectorV2 | ||
{...props} | ||
filterLabel='Filter pipelines' | ||
listApi={async ( | ||
page_token?: string, | ||
page_size?: number, | ||
sort_by?: string, | ||
filter?: string, | ||
) => { | ||
const response = await Apis.pipelineServiceApiV2.listPipelines( | ||
buildInfo?.apiServerMultiUser && view === NamespacedAndSharedTab.NAMESPACED | ||
? props.namespace | ||
: undefined, | ||
page_token, | ||
page_size, | ||
sort_by, | ||
filter, | ||
); | ||
return { | ||
nextPageToken: response.next_page_token || '', | ||
resources: response.pipelines?.map(p => convertPipelineToResource(p)) || [], | ||
}; | ||
}} | ||
columns={props.pipelineSelectorColumns} | ||
emptyMessage='No pipelines found. Upload a pipeline and then try again.' | ||
initialSortColumn={PipelineSortKeys.CREATED_AT} | ||
selectionChanged={async (selectedId: string) => { | ||
const selectedPipeline = await Apis.pipelineServiceApiV2.getPipeline(selectedId); | ||
setUnconfirmedSelectedPipeline(selectedPipeline); | ||
}} | ||
/> | ||
); | ||
} | ||
|
||
function getTabs(): JSX.Element | null { | ||
if (!buildInfo?.apiServerMultiUser) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<MD2Tabs | ||
tabs={[ | ||
{ | ||
header: PipelineTabsHeaders.PRIVATE, | ||
tooltip: PipelineTabsTooltips.PRIVATE, | ||
}, | ||
{ | ||
header: PipelineTabsHeaders.SHARED, | ||
tooltip: PipelineTabsTooltips.SHARED, | ||
}, | ||
]} | ||
selectedTab={view} | ||
onSwitch={tabSwitched} | ||
/> | ||
); | ||
} | ||
|
||
function tabSwitched(newTab: NamespacedAndSharedTab): void { | ||
setUnconfirmedSelectedPipeline(undefined); | ||
setView(newTab); | ||
} | ||
|
||
function closeAndResetState(): void { | ||
props.onClose(false); | ||
setUnconfirmedSelectedPipeline(undefined); | ||
setView(NamespacedAndSharedTab.NAMESPACED); | ||
} | ||
|
||
const getToolbar = (): JSX.Element => { | ||
let actions = new Buttons(props, () => {}).getToolbarActionMap(); | ||
if (props.toolbarActionMap) { | ||
actions = props.toolbarActionMap; | ||
} | ||
return <Toolbar actions={actions} breadcrumbs={[]} pageTitle={'Choose a pipeline'} />; | ||
}; | ||
|
||
return ( | ||
<Dialog | ||
open={props.open} | ||
classes={{ paper: props.selectorDialog }} | ||
onClose={() => closeAndResetState()} | ||
PaperProps={{ id: 'pipelineSelectorDialog' }} | ||
> | ||
<DialogContent> | ||
{getToolbar()} | ||
<div className={classes(commonCss.page, padding(20, 't'))}> | ||
{getTabs()} | ||
|
||
{view === NamespacedAndSharedTab.NAMESPACED && getPipelinesList()} | ||
{view === NamespacedAndSharedTab.SHARED && getPipelinesList()} | ||
</div> | ||
</DialogContent> | ||
<DialogActions> | ||
<Button | ||
id='cancelPipelineSelectionBtn' | ||
onClick={() => closeAndResetState()} | ||
color='secondary' | ||
> | ||
Cancel | ||
</Button> | ||
<Button | ||
id='usePipelineBtn' | ||
onClick={() => props.onClose(true, unconfirmedSelectedPipeline)} | ||
color='secondary' | ||
disabled={!unconfirmedSelectedPipeline} | ||
> | ||
Use this pipeline | ||
</Button> | ||
</DialogActions> | ||
</Dialog> | ||
); | ||
}; | ||
|
||
export default PipelinesDialogV2; |
Oops, something went wrong.