feat(frontend): Support cloning recurringRun in KFP v2 (#8652)
* Added Recurring run control component in the NewRunV2. * Added recurring run details in apiRun. * Added recurringRun creation logic. * Remove unused comment. * temporarily add small tag v2 followed by "start a new run" for differenciate the diff between v1 and v2 * Add log to check the information in the run / job details. * Get IR from apiJob if the run is a run of recurring run. * Add comments. * Remove console.log() * Add isClone back. * Format * Remove setIsClone * Add unit tests. * Remove unused comment. * 1. Remove unused comment 2. Set default value (as 10) for max concurrent runs 3. Add pre-check for max concurrent runs field 4. Update unit tests * 1. Change job to recurring run in comments 2. Redirect to details page rather than list page if id is existing. * Change naming from job to recurring run. * Fix pre-check message. * Fix pre-check message. * Fix pre-check message. * Update unit tests' snapshots. * 1. Enable to retrieve recurring details for cloning. 2. Hide pipeline section for cloning recurring run 3. Disable the run type switcher for cloning. * Enable cloning runtime config. * Enable cloning run trigger. * 1. Change the error message text. 2. Remove the prop isMaxConcurrentRunValid 3. Update the snapshot 4. Remove react.fragment for simplification * 1. Added negative integer condition for max concurrent run pre-check. 2. Added unit tests. * Remove unused import. * Updated a unit test. * 1. Use trigger prop in existing recurring run as the initial prop of useState(). 2. Add unit tests for both UI-created and SDK-created recurring run. * Remove comments. * remove react.fragment * Remove unused const in unit tests. * Remove unused import item. * Format. * 1. Add pre-check for initial trigger and max concurrent runs. 2. Simplify the logic to determine the source of pipeline in NewRunSwitcher. 3. Wrap PipelineUrlLabel expression as a helper function. 4. Add new error if apiRun and apiRecurringRun exist at the same time. * Define a helper type cloneOrigin to integrate apiRun and apiRecurring run to simplify the logic in the NewRunV2. * Improve the logic in getPipelineDetailsUrl function. * Remove unnecessary useState. Simplify the variable type in getCloneOrigin() and NewRunV2 interface. * Add recurring run object assign logic back.
This commit is contained in:
parent
834d966033
commit
80c0dc50db
|
|
@ -54,7 +54,7 @@ interface NewRunParametersProps {
|
||||||
pipelineRoot?: string;
|
pipelineRoot?: string;
|
||||||
// ComponentInputsSpec_ParameterSpec
|
// ComponentInputsSpec_ParameterSpec
|
||||||
specParameters: SpecParameters;
|
specParameters: SpecParameters;
|
||||||
clonedRuntimeConfig: PipelineSpecRuntimeConfig;
|
clonedRuntimeConfig?: PipelineSpecRuntimeConfig;
|
||||||
handlePipelineRootChange?: (pipelineRoot: string) => void;
|
handlePipelineRootChange?: (pipelineRoot: string) => void;
|
||||||
handleParameterChange?: (parameters: RuntimeParameters) => void;
|
handleParameterChange?: (parameters: RuntimeParameters) => void;
|
||||||
setIsValidInput?: (isValid: boolean) => void;
|
setIsValidInput?: (isValid: boolean) => void;
|
||||||
|
|
@ -164,7 +164,7 @@ function NewRunParametersV2(props: NewRunParametersProps) {
|
||||||
|
|
||||||
const [updatedParameters, setUpdatedParameters] = useState({});
|
const [updatedParameters, setUpdatedParameters] = useState({});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (clonedRuntimeConfig.parameters) {
|
if (clonedRuntimeConfig && clonedRuntimeConfig.parameters) {
|
||||||
const clonedRuntimeParametersStr: RuntimeParameters = {};
|
const clonedRuntimeParametersStr: RuntimeParameters = {};
|
||||||
// Convert cloned parameter to string type first to avoid error from convertInput
|
// Convert cloned parameter to string type first to avoid error from convertInput
|
||||||
Object.entries(clonedRuntimeConfig.parameters).forEach(entry => {
|
Object.entries(clonedRuntimeConfig.parameters).forEach(entry => {
|
||||||
|
|
@ -181,7 +181,7 @@ function NewRunParametersV2(props: NewRunParametersProps) {
|
||||||
setIsValidInput(true);
|
setIsValidInput(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handleParameterChange) {
|
if (clonedRuntimeConfig && handleParameterChange) {
|
||||||
handleParameterChange(clonedRuntimeConfig.parameters);
|
handleParameterChange(clonedRuntimeConfig.parameters);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { isTemplateV2 } from 'src/lib/v2/WorkflowUtils';
|
||||||
import { ApiPipeline, ApiPipelineVersion } from 'src/apis/pipeline';
|
import { ApiPipeline, ApiPipelineVersion } from 'src/apis/pipeline';
|
||||||
import { ApiRunDetail } from 'src/apis/run';
|
import { ApiRunDetail } from 'src/apis/run';
|
||||||
import { ApiExperiment } from 'src/apis/experiment';
|
import { ApiExperiment } from 'src/apis/experiment';
|
||||||
|
import { ApiJob } from 'src/apis/job';
|
||||||
|
|
||||||
function NewRunSwitcher(props: PageProps) {
|
function NewRunSwitcher(props: PageProps) {
|
||||||
const namespace = React.useContext(NamespaceContext);
|
const namespace = React.useContext(NamespaceContext);
|
||||||
|
|
@ -23,6 +24,7 @@ function NewRunSwitcher(props: PageProps) {
|
||||||
// runID query by cloneFromRun will be deprecated once v1 is deprecated.
|
// runID query by cloneFromRun will be deprecated once v1 is deprecated.
|
||||||
const originalRunId = urlParser.get(QUERY_PARAMS.cloneFromRun);
|
const originalRunId = urlParser.get(QUERY_PARAMS.cloneFromRun);
|
||||||
const embeddedRunId = urlParser.get(QUERY_PARAMS.fromRunId);
|
const embeddedRunId = urlParser.get(QUERY_PARAMS.fromRunId);
|
||||||
|
const originalRecurringRunId = urlParser.get(QUERY_PARAMS.cloneFromRecurringRun);
|
||||||
const [pipelineId, setPipelineId] = useState(urlParser.get(QUERY_PARAMS.pipelineId));
|
const [pipelineId, setPipelineId] = useState(urlParser.get(QUERY_PARAMS.pipelineId));
|
||||||
const experimentId = urlParser.get(QUERY_PARAMS.experimentId);
|
const experimentId = urlParser.get(QUERY_PARAMS.experimentId);
|
||||||
const [pipelineVersionIdParam, setPipelineVersionIdParam] = useState(
|
const [pipelineVersionIdParam, setPipelineVersionIdParam] = useState(
|
||||||
|
|
@ -30,7 +32,8 @@ function NewRunSwitcher(props: PageProps) {
|
||||||
);
|
);
|
||||||
const existingRunId = originalRunId ? originalRunId : embeddedRunId;
|
const existingRunId = originalRunId ? originalRunId : embeddedRunId;
|
||||||
|
|
||||||
const { isSuccess: runIsSuccess, isFetching: runIsFetching, data: apiRun } = useQuery<
|
// Retrieve run details
|
||||||
|
const { isSuccess: getRunSuccess, isFetching: runIsFetching, data: apiRun } = useQuery<
|
||||||
ApiRunDetail,
|
ApiRunDetail,
|
||||||
Error
|
Error
|
||||||
>(
|
>(
|
||||||
|
|
@ -43,7 +46,32 @@ function NewRunSwitcher(props: PageProps) {
|
||||||
},
|
},
|
||||||
{ enabled: !!existingRunId, staleTime: Infinity },
|
{ enabled: !!existingRunId, staleTime: Infinity },
|
||||||
);
|
);
|
||||||
const templateStrFromRunId = apiRun ? apiRun.run?.pipeline_spec?.pipeline_manifest : '';
|
|
||||||
|
// Retrieve recurring run details
|
||||||
|
const {
|
||||||
|
isSuccess: getRecurringRunSuccess,
|
||||||
|
isFetching: recurringRunIsFetching,
|
||||||
|
data: apiRecurringRun,
|
||||||
|
} = useQuery<ApiJob, Error>(
|
||||||
|
['ApiRecurringRun', originalRecurringRunId],
|
||||||
|
() => {
|
||||||
|
if (!originalRecurringRunId) {
|
||||||
|
throw new Error('Recurring Run ID is missing');
|
||||||
|
}
|
||||||
|
return Apis.jobServiceApi.getJob(originalRecurringRunId);
|
||||||
|
},
|
||||||
|
{ enabled: !!originalRecurringRunId, staleTime: Infinity },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (apiRun !== undefined && apiRecurringRun !== undefined) {
|
||||||
|
throw new Error('The existence of run and recurring run should be exclusive.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// template string from cloned object
|
||||||
|
let pipelineManifest = apiRun?.run?.pipeline_spec?.pipeline_manifest;
|
||||||
|
if (getRecurringRunSuccess && apiRecurringRun) {
|
||||||
|
pipelineManifest = apiRecurringRun.pipeline_spec?.pipeline_manifest;
|
||||||
|
}
|
||||||
|
|
||||||
const { isFetching: pipelineIsFetching, data: apiPipeline } = useQuery<ApiPipeline, Error>(
|
const { isFetching: pipelineIsFetching, data: apiPipeline } = useQuery<ApiPipeline, Error>(
|
||||||
['ApiPipeline', pipelineId],
|
['ApiPipeline', pipelineId],
|
||||||
|
|
@ -99,17 +127,21 @@ function NewRunSwitcher(props: PageProps) {
|
||||||
{ enabled: !!experimentId, staleTime: Infinity },
|
{ enabled: !!experimentId, staleTime: Infinity },
|
||||||
);
|
);
|
||||||
|
|
||||||
const templateString =
|
const templateString = pipelineManifest ? pipelineManifest : templateStrFromPipelineId;
|
||||||
templateStrFromRunId === '' ? templateStrFromPipelineId : templateStrFromRunId;
|
|
||||||
|
|
||||||
if (isFeatureEnabled(FeatureKey.V2_ALPHA)) {
|
if (isFeatureEnabled(FeatureKey.V2_ALPHA)) {
|
||||||
if ((runIsSuccess || isTemplatePullSuccessFromPipeline) && isTemplateV2(templateString || '')) {
|
if (
|
||||||
|
(getRunSuccess || getRecurringRunSuccess || isTemplatePullSuccessFromPipeline) &&
|
||||||
|
isTemplateV2(templateString || '')
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<NewRunV2
|
<NewRunV2
|
||||||
{...props}
|
{...props}
|
||||||
namespace={namespace}
|
namespace={namespace}
|
||||||
existingRunId={existingRunId}
|
existingRunId={existingRunId}
|
||||||
apiRun={apiRun}
|
apiRun={apiRun}
|
||||||
|
originalRecurringRunId={originalRecurringRunId}
|
||||||
|
apiRecurringRun={apiRecurringRun}
|
||||||
existingPipeline={apiPipeline}
|
existingPipeline={apiPipeline}
|
||||||
handlePipelineIdChange={setPipelineId}
|
handlePipelineIdChange={setPipelineId}
|
||||||
existingPipelineVersion={apiPipelineVersion}
|
existingPipelineVersion={apiPipelineVersion}
|
||||||
|
|
@ -126,6 +158,7 @@ function NewRunSwitcher(props: PageProps) {
|
||||||
// TODO(jlyaoyuli): set v2 as default once v1 is deprecated.
|
// TODO(jlyaoyuli): set v2 as default once v1 is deprecated.
|
||||||
if (
|
if (
|
||||||
runIsFetching ||
|
runIsFetching ||
|
||||||
|
recurringRunIsFetching ||
|
||||||
pipelineIsFetching ||
|
pipelineIsFetching ||
|
||||||
pipelineVersionIsFetching ||
|
pipelineVersionIsFetching ||
|
||||||
pipelineTemplateStrIsFetching
|
pipelineTemplateStrIsFetching
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,22 @@ describe('NewRunV2', () => {
|
||||||
name: NEW_TEST_PIPELINE_VERSION_NAME,
|
name: NEW_TEST_PIPELINE_VERSION_NAME,
|
||||||
description: '',
|
description: '',
|
||||||
};
|
};
|
||||||
|
const TEST_RESOURCE_REFERENCE = [
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
id: '275ea11d-ac63-4ce3-bc33-ec81981ed56b',
|
||||||
|
type: ApiResourceType.EXPERIMENT,
|
||||||
|
},
|
||||||
|
relationship: ApiRelationship.OWNER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
id: ORIGINAL_TEST_PIPELINE_VERSION_ID,
|
||||||
|
type: ApiResourceType.PIPELINEVERSION,
|
||||||
|
},
|
||||||
|
relationship: ApiRelationship.CREATOR,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Reponse from BE while POST a run for creating New UI-Run
|
// Reponse from BE while POST a run for creating New UI-Run
|
||||||
const API_UI_CREATED_NEW_RUN_DETAILS: ApiRunDetail = {
|
const API_UI_CREATED_NEW_RUN_DETAILS: ApiRunDetail = {
|
||||||
|
|
@ -106,37 +122,12 @@ describe('NewRunV2', () => {
|
||||||
pipeline_manifest: v2YamlTemplateString,
|
pipeline_manifest: v2YamlTemplateString,
|
||||||
runtime_config: { parameters: { intParam: 123 } },
|
runtime_config: { parameters: { intParam: 123 } },
|
||||||
},
|
},
|
||||||
resource_references: [
|
resource_references: TEST_RESOURCE_REFERENCE,
|
||||||
{
|
|
||||||
key: {
|
|
||||||
id: '275ea11d-ac63-4ce3-bc33-ec81981ed56b',
|
|
||||||
type: ApiResourceType.EXPERIMENT,
|
|
||||||
},
|
|
||||||
relationship: ApiRelationship.OWNER,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: {
|
|
||||||
id: ORIGINAL_TEST_PIPELINE_VERSION_ID,
|
|
||||||
type: ApiResourceType.PIPELINEVERSION,
|
|
||||||
},
|
|
||||||
relationship: ApiRelationship.CREATOR,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
scheduled_at: new Date('2021-05-17T20:58:23.000Z'),
|
scheduled_at: new Date('2021-05-17T20:58:23.000Z'),
|
||||||
status: 'Succeeded',
|
status: 'Succeeded',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const API_UI_CREATED_NEW_RECURRING_RUN_DETAILS: ApiJob = {
|
|
||||||
created_at: new Date('2021-05-17T20:58:23.000Z'),
|
|
||||||
description: 'V2 xgboost',
|
|
||||||
id: TEST_RECURRING_RUN_ID,
|
|
||||||
name: 'Run of v2-xgboost-ilbo',
|
|
||||||
pipeline_spec: {
|
|
||||||
pipeline_manifest: v2YamlTemplateString,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reponse from BE while POST a run for cloning UI-Run
|
// Reponse from BE while POST a run for cloning UI-Run
|
||||||
const API_UI_CREATED_CLONING_RUN_DETAILS: ApiRunDetail = {
|
const API_UI_CREATED_CLONING_RUN_DETAILS: ApiRunDetail = {
|
||||||
pipeline_runtime: {
|
pipeline_runtime: {
|
||||||
|
|
@ -152,22 +143,7 @@ describe('NewRunV2', () => {
|
||||||
pipeline_manifest: v2YamlTemplateString,
|
pipeline_manifest: v2YamlTemplateString,
|
||||||
runtime_config: { parameters: { intParam: 123 } },
|
runtime_config: { parameters: { intParam: 123 } },
|
||||||
},
|
},
|
||||||
resource_references: [
|
resource_references: TEST_RESOURCE_REFERENCE,
|
||||||
{
|
|
||||||
key: {
|
|
||||||
id: '275ea11d-ac63-4ce3-bc33-ec81981ed56b',
|
|
||||||
type: ApiResourceType.EXPERIMENT,
|
|
||||||
},
|
|
||||||
relationship: ApiRelationship.OWNER,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: {
|
|
||||||
id: ORIGINAL_TEST_PIPELINE_VERSION_ID,
|
|
||||||
type: ApiResourceType.PIPELINEVERSION,
|
|
||||||
},
|
|
||||||
relationship: ApiRelationship.CREATOR,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
scheduled_at: new Date('2022-08-12T20:58:23.000Z'),
|
scheduled_at: new Date('2022-08-12T20:58:23.000Z'),
|
||||||
status: 'Succeeded',
|
status: 'Succeeded',
|
||||||
},
|
},
|
||||||
|
|
@ -231,6 +207,86 @@ describe('NewRunV2', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const API_UI_CREATED_NEW_RECURRING_RUN_DETAILS: ApiJob = {
|
||||||
|
created_at: new Date('2021-05-17T20:58:23.000Z'),
|
||||||
|
description: 'V2 xgboost',
|
||||||
|
id: TEST_RECURRING_RUN_ID,
|
||||||
|
name: 'Run of v2-xgboost-ilbo',
|
||||||
|
pipeline_spec: {
|
||||||
|
pipeline_manifest: v2YamlTemplateString,
|
||||||
|
runtime_config: { parameters: { intParam: 123 } },
|
||||||
|
},
|
||||||
|
resource_references: TEST_RESOURCE_REFERENCE,
|
||||||
|
trigger: {
|
||||||
|
periodic_schedule: { interval_second: '3600' },
|
||||||
|
},
|
||||||
|
max_concurrency: '10',
|
||||||
|
};
|
||||||
|
|
||||||
|
const API_UI_CREATED_CLONING_RECURRING_RUN_DETAILS: ApiJob = {
|
||||||
|
created_at: new Date('2023-01-04T20:58:23.000Z'),
|
||||||
|
description: 'V2 xgboost',
|
||||||
|
id: 'test-clone-ui-recurring-run-id',
|
||||||
|
name: 'Clone of Run of v2-xgboost-ilbo',
|
||||||
|
pipeline_spec: {
|
||||||
|
pipeline_manifest: v2YamlTemplateString,
|
||||||
|
runtime_config: { parameters: { intParam: 123 } },
|
||||||
|
},
|
||||||
|
resource_references: TEST_RESOURCE_REFERENCE,
|
||||||
|
trigger: {
|
||||||
|
periodic_schedule: { interval_second: '3600' },
|
||||||
|
},
|
||||||
|
max_concurrency: '10',
|
||||||
|
};
|
||||||
|
|
||||||
|
const API_SDK_CREATED_NEW_RECURRING_RUN_DETAILS: ApiJob = {
|
||||||
|
created_at: new Date('2021-05-17T20:58:23.000Z'),
|
||||||
|
description: 'V2 xgboost',
|
||||||
|
id: TEST_RECURRING_RUN_ID,
|
||||||
|
name: 'Run of v2-xgboost-ilbo',
|
||||||
|
pipeline_spec: {
|
||||||
|
pipeline_manifest: v2YamlTemplateString,
|
||||||
|
runtime_config: { parameters: { intParam: 123 } },
|
||||||
|
},
|
||||||
|
resource_references: [
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
id: '275ea11d-ac63-4ce3-bc33-ec81981ed56b',
|
||||||
|
type: ApiResourceType.EXPERIMENT,
|
||||||
|
},
|
||||||
|
relationship: ApiRelationship.OWNER,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
trigger: {
|
||||||
|
periodic_schedule: { interval_second: '3600' },
|
||||||
|
},
|
||||||
|
max_concurrency: '10',
|
||||||
|
};
|
||||||
|
|
||||||
|
const API_SDK_CREATED_CLONING_RECURRING_RUN_DETAILS: ApiJob = {
|
||||||
|
created_at: new Date('2023-01-04T20:58:23.000Z'),
|
||||||
|
description: 'V2 xgboost',
|
||||||
|
id: 'test-clone-ui-recurring-run-id',
|
||||||
|
name: 'Clone of Run of v2-xgboost-ilbo',
|
||||||
|
pipeline_spec: {
|
||||||
|
pipeline_manifest: v2YamlTemplateString,
|
||||||
|
runtime_config: { parameters: { intParam: 123 } },
|
||||||
|
},
|
||||||
|
resource_references: [
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
id: '275ea11d-ac63-4ce3-bc33-ec81981ed56b',
|
||||||
|
type: ApiResourceType.EXPERIMENT,
|
||||||
|
},
|
||||||
|
relationship: ApiRelationship.OWNER,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
trigger: {
|
||||||
|
periodic_schedule: { interval_second: '3600' },
|
||||||
|
},
|
||||||
|
max_concurrency: '10',
|
||||||
|
};
|
||||||
|
|
||||||
const DEFAULT_EXPERIMENT: ApiExperiment = {
|
const DEFAULT_EXPERIMENT: ApiExperiment = {
|
||||||
created_at: new Date('2022-07-14T21:26:58Z'),
|
created_at: new Date('2022-07-14T21:26:58Z'),
|
||||||
id: '796eb126-dd76-44de-a21f-d70010c6a029',
|
id: '796eb126-dd76-44de-a21f-d70010c6a029',
|
||||||
|
|
@ -302,6 +358,8 @@ describe('NewRunV2', () => {
|
||||||
{...generatePropsNewRun()}
|
{...generatePropsNewRun()}
|
||||||
existingRunId='e0115ac1-0479-4194-a22d-01e65e09a32b'
|
existingRunId='e0115ac1-0479-4194-a22d-01e65e09a32b'
|
||||||
apiRun={undefined}
|
apiRun={undefined}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
||||||
|
|
@ -337,6 +395,8 @@ describe('NewRunV2', () => {
|
||||||
{...generatePropsNewRun()}
|
{...generatePropsNewRun()}
|
||||||
existingRunId='e0115ac1-0479-4194-a22d-01e65e09a32b'
|
existingRunId='e0115ac1-0479-4194-a22d-01e65e09a32b'
|
||||||
apiRun={undefined}
|
apiRun={undefined}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
||||||
|
|
@ -370,6 +430,8 @@ describe('NewRunV2', () => {
|
||||||
{...generatePropsNewRun()}
|
{...generatePropsNewRun()}
|
||||||
existingRunId='e0115ac1-0479-4194-a22d-01e65e09a32b'
|
existingRunId='e0115ac1-0479-4194-a22d-01e65e09a32b'
|
||||||
apiRun={undefined}
|
apiRun={undefined}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
||||||
|
|
@ -406,6 +468,8 @@ describe('NewRunV2', () => {
|
||||||
{...generatePropsNewRun()}
|
{...generatePropsNewRun()}
|
||||||
existingRunId={null}
|
existingRunId={null}
|
||||||
apiRun={undefined}
|
apiRun={undefined}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
||||||
|
|
@ -447,6 +511,8 @@ describe('NewRunV2', () => {
|
||||||
{...generatePropsNewRun()}
|
{...generatePropsNewRun()}
|
||||||
existingRunId={null}
|
existingRunId={null}
|
||||||
apiRun={undefined}
|
apiRun={undefined}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
||||||
|
|
@ -502,6 +568,8 @@ describe('NewRunV2', () => {
|
||||||
namespace='test-ns'
|
namespace='test-ns'
|
||||||
existingRunId={null}
|
existingRunId={null}
|
||||||
apiRun={undefined}
|
apiRun={undefined}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
||||||
|
|
@ -546,6 +614,8 @@ describe('NewRunV2', () => {
|
||||||
namespace='test-ns'
|
namespace='test-ns'
|
||||||
existingRunId={null}
|
existingRunId={null}
|
||||||
apiRun={undefined}
|
apiRun={undefined}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
||||||
|
|
@ -590,6 +660,8 @@ describe('NewRunV2', () => {
|
||||||
namespace='test-ns'
|
namespace='test-ns'
|
||||||
existingRunId={null}
|
existingRunId={null}
|
||||||
apiRun={undefined}
|
apiRun={undefined}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
||||||
|
|
@ -641,6 +713,8 @@ describe('NewRunV2', () => {
|
||||||
{...generatePropsNewRun()}
|
{...generatePropsNewRun()}
|
||||||
existingRunId={null}
|
existingRunId={null}
|
||||||
apiRun={undefined}
|
apiRun={undefined}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
||||||
|
|
@ -668,7 +742,7 @@ describe('NewRunV2', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('creating a recurring run', () => {
|
describe('creating a recurring run', () => {
|
||||||
it('displays run trigger section', async () => {
|
it('submits a new recurring run', async () => {
|
||||||
const createJobSpy = jest.spyOn(Apis.jobServiceApi, 'createJob');
|
const createJobSpy = jest.spyOn(Apis.jobServiceApi, 'createJob');
|
||||||
createJobSpy.mockResolvedValue(API_UI_CREATED_NEW_RECURRING_RUN_DETAILS);
|
createJobSpy.mockResolvedValue(API_UI_CREATED_NEW_RECURRING_RUN_DETAILS);
|
||||||
|
|
||||||
|
|
@ -678,6 +752,8 @@ describe('NewRunV2', () => {
|
||||||
{...generatePropsNewRun()}
|
{...generatePropsNewRun()}
|
||||||
existingRunId={null}
|
existingRunId={null}
|
||||||
apiRun={undefined}
|
apiRun={undefined}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
||||||
|
|
@ -730,6 +806,8 @@ describe('NewRunV2', () => {
|
||||||
{...generatePropsNewRun()}
|
{...generatePropsNewRun()}
|
||||||
existingRunId={null}
|
existingRunId={null}
|
||||||
apiRun={undefined}
|
apiRun={undefined}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
existingPipeline={ORIGINAL_TEST_PIPELINE}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
existingPipelineVersion={ORIGINAL_TEST_PIPELINE_VERSION}
|
||||||
|
|
@ -831,7 +909,7 @@ describe('NewRunV2', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('cloning a existing run', () => {
|
describe('cloning an existing run', () => {
|
||||||
it('only shows clone run name from original run', () => {
|
it('only shows clone run name from original run', () => {
|
||||||
render(
|
render(
|
||||||
<CommonTestWrapper>
|
<CommonTestWrapper>
|
||||||
|
|
@ -839,6 +917,8 @@ describe('NewRunV2', () => {
|
||||||
{...generatePropsClonedRun()}
|
{...generatePropsClonedRun()}
|
||||||
existingRunId='e0115ac1-0479-4194-a22d-01e65e09a32b'
|
existingRunId='e0115ac1-0479-4194-a22d-01e65e09a32b'
|
||||||
apiRun={API_UI_CREATED_NEW_RUN_DETAILS}
|
apiRun={API_UI_CREATED_NEW_RUN_DETAILS}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={undefined}
|
existingPipeline={undefined}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={undefined}
|
existingPipelineVersion={undefined}
|
||||||
|
|
@ -861,6 +941,8 @@ describe('NewRunV2', () => {
|
||||||
{...generatePropsClonedRun()}
|
{...generatePropsClonedRun()}
|
||||||
existingRunId={TEST_RUN_ID}
|
existingRunId={TEST_RUN_ID}
|
||||||
apiRun={API_UI_CREATED_NEW_RUN_DETAILS}
|
apiRun={API_UI_CREATED_NEW_RUN_DETAILS}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={undefined}
|
existingPipeline={undefined}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={undefined}
|
existingPipelineVersion={undefined}
|
||||||
|
|
@ -887,22 +969,7 @@ describe('NewRunV2', () => {
|
||||||
pipeline_manifest: undefined,
|
pipeline_manifest: undefined,
|
||||||
runtime_config: { parameters: { intParam: 123 } },
|
runtime_config: { parameters: { intParam: 123 } },
|
||||||
},
|
},
|
||||||
resource_references: [
|
resource_references: TEST_RESOURCE_REFERENCE,
|
||||||
{
|
|
||||||
key: {
|
|
||||||
id: '275ea11d-ac63-4ce3-bc33-ec81981ed56b',
|
|
||||||
type: ApiResourceType.EXPERIMENT,
|
|
||||||
},
|
|
||||||
relationship: ApiRelationship.OWNER,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: {
|
|
||||||
id: ORIGINAL_TEST_PIPELINE_VERSION_ID,
|
|
||||||
type: ApiResourceType.PIPELINEVERSION,
|
|
||||||
},
|
|
||||||
relationship: ApiRelationship.CREATOR,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
service_account: '',
|
service_account: '',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -919,6 +986,8 @@ describe('NewRunV2', () => {
|
||||||
{...generatePropsClonedRun()}
|
{...generatePropsClonedRun()}
|
||||||
existingRunId={TEST_RUN_ID}
|
existingRunId={TEST_RUN_ID}
|
||||||
apiRun={API_SDK_CREATED_NEW_RUN_DETAILS}
|
apiRun={API_SDK_CREATED_NEW_RUN_DETAILS}
|
||||||
|
originalRecurringRunId={null}
|
||||||
|
apiRecurringRun={undefined}
|
||||||
existingPipeline={undefined}
|
existingPipeline={undefined}
|
||||||
handlePipelineIdChange={jest.fn()}
|
handlePipelineIdChange={jest.fn()}
|
||||||
existingPipelineVersion={undefined}
|
existingPipelineVersion={undefined}
|
||||||
|
|
@ -960,4 +1029,124 @@ describe('NewRunV2', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('clone an existing recurring run', () => {
|
||||||
|
it('submits a recurring run with same runtimeConfig and trigger from clone UI-created recurring run', async () => {
|
||||||
|
const createJobSpy = jest.spyOn(Apis.jobServiceApi, 'createJob');
|
||||||
|
createJobSpy.mockResolvedValue(API_UI_CREATED_CLONING_RECURRING_RUN_DETAILS);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CommonTestWrapper>
|
||||||
|
<NewRunV2
|
||||||
|
{...generatePropsClonedRun()}
|
||||||
|
existingRunId={null}
|
||||||
|
apiRun={undefined}
|
||||||
|
originalRecurringRunId={TEST_RECURRING_RUN_ID}
|
||||||
|
apiRecurringRun={API_UI_CREATED_NEW_RECURRING_RUN_DETAILS}
|
||||||
|
existingPipeline={undefined}
|
||||||
|
handlePipelineIdChange={jest.fn()}
|
||||||
|
existingPipelineVersion={undefined}
|
||||||
|
handlePipelineVersionIdChange={jest.fn()}
|
||||||
|
templateString={v2YamlTemplateString}
|
||||||
|
chosenExperiment={undefined}
|
||||||
|
/>
|
||||||
|
</CommonTestWrapper>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const startButton = await screen.findByText('Start');
|
||||||
|
// Because start button is set false by default
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(startButton.closest('button')?.disabled).toEqual(false);
|
||||||
|
});
|
||||||
|
fireEvent.click(startButton);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(createJobSpy).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
description: '',
|
||||||
|
name: 'Clone of Run of v2-xgboost-ilbo',
|
||||||
|
pipeline_spec: {
|
||||||
|
pipeline_manifest: undefined,
|
||||||
|
runtime_config: { parameters: { intParam: 123 } },
|
||||||
|
},
|
||||||
|
resource_references: TEST_RESOURCE_REFERENCE,
|
||||||
|
trigger: {
|
||||||
|
periodic_schedule: { interval_second: '3600' },
|
||||||
|
},
|
||||||
|
max_concurrency: '10',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(updateSnackbarSpy).toHaveBeenLastCalledWith({
|
||||||
|
message: 'Successfully started new recurring Run: Clone of Run of v2-xgboost-ilbo',
|
||||||
|
open: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('submits a recurring run with same runtimeConfig and trigger from clone SDK-created recurring run', async () => {
|
||||||
|
const createJobSpy = jest.spyOn(Apis.jobServiceApi, 'createJob');
|
||||||
|
createJobSpy.mockResolvedValue(API_SDK_CREATED_CLONING_RECURRING_RUN_DETAILS);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CommonTestWrapper>
|
||||||
|
<NewRunV2
|
||||||
|
{...generatePropsClonedRun()}
|
||||||
|
existingRunId={null}
|
||||||
|
apiRun={undefined}
|
||||||
|
originalRecurringRunId={TEST_RECURRING_RUN_ID}
|
||||||
|
apiRecurringRun={API_SDK_CREATED_NEW_RECURRING_RUN_DETAILS}
|
||||||
|
existingPipeline={undefined}
|
||||||
|
handlePipelineIdChange={jest.fn()}
|
||||||
|
existingPipelineVersion={undefined}
|
||||||
|
handlePipelineVersionIdChange={jest.fn()}
|
||||||
|
templateString={v2YamlTemplateString}
|
||||||
|
chosenExperiment={undefined}
|
||||||
|
/>
|
||||||
|
</CommonTestWrapper>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const startButton = await screen.findByText('Start');
|
||||||
|
// Because start button is set false by default
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(startButton.closest('button')?.disabled).toEqual(false);
|
||||||
|
});
|
||||||
|
fireEvent.click(startButton);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(createJobSpy).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
description: '',
|
||||||
|
name: 'Clone of Run of v2-xgboost-ilbo',
|
||||||
|
pipeline_spec: {
|
||||||
|
pipeline_manifest: v2YamlTemplateString,
|
||||||
|
runtime_config: { parameters: { intParam: 123 } },
|
||||||
|
},
|
||||||
|
resource_references: [
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
id: '275ea11d-ac63-4ce3-bc33-ec81981ed56b',
|
||||||
|
type: ApiResourceType.EXPERIMENT,
|
||||||
|
},
|
||||||
|
relationship: ApiRelationship.OWNER,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
trigger: {
|
||||||
|
periodic_schedule: { interval_second: '3600' },
|
||||||
|
},
|
||||||
|
max_concurrency: '10',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(updateSnackbarSpy).toHaveBeenLastCalledWith({
|
||||||
|
message: 'Successfully started new recurring Run: Clone of Run of v2-xgboost-ilbo',
|
||||||
|
open: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import { useMutation } from 'react-query';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { ApiExperiment, ApiExperimentStorageState } from 'src/apis/experiment';
|
import { ApiExperiment, ApiExperimentStorageState } from 'src/apis/experiment';
|
||||||
import { ApiFilter, PredicateOp } from 'src/apis/filter';
|
import { ApiFilter, PredicateOp } from 'src/apis/filter';
|
||||||
import { ApiJob, ApiTrigger } from 'src/apis/job';
|
import { ApiJob } from 'src/apis/job';
|
||||||
import { ApiPipeline, ApiPipelineVersion } from 'src/apis/pipeline';
|
import { ApiPipeline, ApiPipelineVersion } from 'src/apis/pipeline';
|
||||||
import {
|
import {
|
||||||
ApiRelationship,
|
ApiRelationship,
|
||||||
|
|
@ -36,7 +36,6 @@ import {
|
||||||
ApiResourceType,
|
ApiResourceType,
|
||||||
ApiRun,
|
ApiRun,
|
||||||
ApiRunDetail,
|
ApiRunDetail,
|
||||||
PipelineSpecRuntimeConfig,
|
|
||||||
} from 'src/apis/run';
|
} from 'src/apis/run';
|
||||||
import BusyButton from 'src/atoms/BusyButton';
|
import BusyButton from 'src/atoms/BusyButton';
|
||||||
import { ExternalLink } from 'src/atoms/ExternalLink';
|
import { ExternalLink } from 'src/atoms/ExternalLink';
|
||||||
|
|
@ -77,13 +76,15 @@ const descriptionCustomRenderer: React.FC<CustomRendererProps<string>> = props =
|
||||||
interface RunV2Props {
|
interface RunV2Props {
|
||||||
namespace?: string;
|
namespace?: string;
|
||||||
existingRunId: string | null;
|
existingRunId: string | null;
|
||||||
apiRun: ApiRunDetail | undefined;
|
apiRun?: ApiRunDetail;
|
||||||
existingPipeline: ApiPipeline | undefined;
|
originalRecurringRunId: string | null;
|
||||||
|
apiRecurringRun?: ApiJob;
|
||||||
|
existingPipeline?: ApiPipeline;
|
||||||
handlePipelineIdChange: (pipelineId: string) => void;
|
handlePipelineIdChange: (pipelineId: string) => void;
|
||||||
existingPipelineVersion: ApiPipelineVersion | undefined;
|
existingPipelineVersion?: ApiPipelineVersion;
|
||||||
handlePipelineVersionIdChange: (pipelineVersionId: string) => void;
|
handlePipelineVersionIdChange: (pipelineVersionId: string) => void;
|
||||||
templateString: string | undefined;
|
templateString?: string;
|
||||||
chosenExperiment: ApiExperiment | undefined;
|
chosenExperiment?: ApiExperiment;
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewRunV2Props = RunV2Props & PageProps;
|
type NewRunV2Props = RunV2Props & PageProps;
|
||||||
|
|
@ -91,24 +92,71 @@ type NewRunV2Props = RunV2Props & PageProps;
|
||||||
export type SpecParameters = { [key: string]: ComponentInputsSpec_ParameterSpec };
|
export type SpecParameters = { [key: string]: ComponentInputsSpec_ParameterSpec };
|
||||||
export type RuntimeParameters = { [key: string]: any };
|
export type RuntimeParameters = { [key: string]: any };
|
||||||
|
|
||||||
function hasVersionID(apiRun: ApiRunDetail | undefined): boolean {
|
type CloneOrigin = {
|
||||||
if (!apiRun) {
|
isClone: boolean;
|
||||||
|
isRecurring: boolean;
|
||||||
|
run?: ApiRunDetail;
|
||||||
|
recurringRun?: ApiJob;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getCloneOrigin(apiRun?: ApiRunDetail, apiRecurringRun?: ApiJob) {
|
||||||
|
let cloneOrigin: CloneOrigin = {
|
||||||
|
isClone: apiRun !== undefined || apiRecurringRun !== undefined,
|
||||||
|
isRecurring: apiRecurringRun !== undefined,
|
||||||
|
run: apiRun,
|
||||||
|
recurringRun: apiRecurringRun,
|
||||||
|
};
|
||||||
|
return cloneOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasVersionID(cloneOrigin: CloneOrigin): boolean {
|
||||||
|
if (!cloneOrigin.isClone) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
let hasVersionType: boolean = false;
|
let hasVersionType: boolean = false;
|
||||||
if (apiRun.run?.resource_references) {
|
const existResourceRef = cloneOrigin.isRecurring
|
||||||
apiRun.run.resource_references.forEach(value => {
|
? cloneOrigin.recurringRun?.resource_references
|
||||||
|
: cloneOrigin.run?.run?.resource_references;
|
||||||
|
if (existResourceRef) {
|
||||||
|
existResourceRef.forEach(value => {
|
||||||
hasVersionType = hasVersionType || value.key?.type === ApiResourceType.PIPELINEVERSION;
|
hasVersionType = hasVersionType || value.key?.type === ApiResourceType.PIPELINEVERSION;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return hasVersionType;
|
return hasVersionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPipelineDetailsUrl(
|
||||||
|
props: NewRunV2Props,
|
||||||
|
isRecurring: boolean,
|
||||||
|
existingRunId: string | null,
|
||||||
|
originalRecurringRunId: string | null,
|
||||||
|
): string {
|
||||||
|
const urlParser = new URLParser(props);
|
||||||
|
|
||||||
|
const pipelineDetailsUrlfromRun = existingRunId
|
||||||
|
? RoutePage.PIPELINE_DETAILS.replace(
|
||||||
|
':' + RouteParams.pipelineId + '/version/:' + RouteParams.pipelineVersionId + '?',
|
||||||
|
'',
|
||||||
|
) + urlParser.build({ [QUERY_PARAMS.fromRunId]: existingRunId })
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const pipelineDetailsUrlfromRecurringRun = originalRecurringRunId
|
||||||
|
? RoutePage.PIPELINE_DETAILS.replace(
|
||||||
|
':' + RouteParams.pipelineId + '/version/:' + RouteParams.pipelineVersionId + '?',
|
||||||
|
'',
|
||||||
|
) + urlParser.build({ [QUERY_PARAMS.cloneFromRecurringRun]: originalRecurringRunId })
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return isRecurring ? pipelineDetailsUrlfromRecurringRun : pipelineDetailsUrlfromRun;
|
||||||
|
}
|
||||||
|
|
||||||
function NewRunV2(props: NewRunV2Props) {
|
function NewRunV2(props: NewRunV2Props) {
|
||||||
// List of elements we need to create Pipeline Run.
|
// List of elements we need to create Pipeline Run.
|
||||||
const {
|
const {
|
||||||
existingRunId,
|
existingRunId,
|
||||||
apiRun,
|
apiRun,
|
||||||
|
originalRecurringRunId,
|
||||||
|
apiRecurringRun,
|
||||||
existingPipeline,
|
existingPipeline,
|
||||||
handlePipelineIdChange,
|
handlePipelineIdChange,
|
||||||
existingPipelineVersion,
|
existingPipelineVersion,
|
||||||
|
|
@ -116,6 +164,7 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
templateString,
|
templateString,
|
||||||
chosenExperiment,
|
chosenExperiment,
|
||||||
} = props;
|
} = props;
|
||||||
|
const cloneOrigin = getCloneOrigin(apiRun, apiRecurringRun);
|
||||||
const [runName, setRunName] = useState('');
|
const [runName, setRunName] = useState('');
|
||||||
const [runDescription, setRunDescription] = useState('');
|
const [runDescription, setRunDescription] = useState('');
|
||||||
const [pipelineName, setPipelineName] = useState('');
|
const [pipelineName, setPipelineName] = useState('');
|
||||||
|
|
@ -131,30 +180,37 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
const [isStartingNewRun, setIsStartingNewRun] = useState(false);
|
const [isStartingNewRun, setIsStartingNewRun] = useState(false);
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
const [errorMessage, setErrorMessage] = useState('');
|
||||||
const [isParameterValid, setIsParameterValid] = useState(false);
|
const [isParameterValid, setIsParameterValid] = useState(false);
|
||||||
const [isRecurringRun, setIsRecurringRun] = useState(false);
|
const [isRecurringRun, setIsRecurringRun] = useState(cloneOrigin.isRecurring);
|
||||||
const [trigger, setTrigger] = useState<ApiTrigger>();
|
const initialTrigger = cloneOrigin.recurringRun?.trigger
|
||||||
const [maxConcurrentRuns, setMaxConcurrentRuns] = useState('10');
|
? cloneOrigin.recurringRun.trigger
|
||||||
|
: undefined;
|
||||||
|
const [trigger, setTrigger] = useState(initialTrigger);
|
||||||
|
const initialMaxConCurrentRuns =
|
||||||
|
cloneOrigin.recurringRun?.max_concurrency !== undefined
|
||||||
|
? cloneOrigin.recurringRun.max_concurrency
|
||||||
|
: '10';
|
||||||
|
const [maxConcurrentRuns, setMaxConcurrentRuns] = useState(initialMaxConCurrentRuns);
|
||||||
const [isMaxConcurrentRunValid, setIsMaxConcurrentRunValid] = useState(true);
|
const [isMaxConcurrentRunValid, setIsMaxConcurrentRunValid] = useState(true);
|
||||||
const [catchup, setCatchup] = useState(true);
|
const initialCatchup =
|
||||||
const [clonedRuntimeConfig, setClonedRuntimeConfig] = useState<PipelineSpecRuntimeConfig>({});
|
cloneOrigin.recurringRun?.no_catchup !== undefined
|
||||||
|
? !cloneOrigin.recurringRun.no_catchup
|
||||||
|
: true;
|
||||||
|
const [needCatchup, setNeedCatchup] = useState(initialCatchup);
|
||||||
|
|
||||||
|
const clonedRuntimeConfig = cloneOrigin.isRecurring
|
||||||
|
? cloneOrigin.recurringRun?.pipeline_spec?.runtime_config
|
||||||
|
: cloneOrigin.run?.run?.pipeline_spec?.runtime_config;
|
||||||
const urlParser = new URLParser(props);
|
const urlParser = new URLParser(props);
|
||||||
const usePipelineFromRunLabel = 'Using pipeline from existing run.';
|
const labelTextAdjective = isRecurringRun ? 'recurring ' : '';
|
||||||
const pipelineDetailsUrl = existingRunId
|
const usePipelineFromRunLabel = `Using pipeline from existing ${labelTextAdjective} run.`;
|
||||||
? RoutePage.PIPELINE_DETAILS.replace(
|
|
||||||
':' + RouteParams.pipelineId + '/version/:' + RouteParams.pipelineVersionId + '?',
|
|
||||||
'',
|
|
||||||
) + urlParser.build({ [QUERY_PARAMS.fromRunId]: existingRunId })
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const isTemplatePullSuccess = templateString ? true : false;
|
const isTemplatePullSuccess = templateString ? true : false;
|
||||||
const apiResourceRefFromRun = apiRun?.run?.resource_references
|
const existResourceRef = cloneOrigin.isRecurring
|
||||||
? apiRun.run?.resource_references
|
? cloneOrigin.recurringRun?.resource_references
|
||||||
: undefined;
|
: cloneOrigin.run?.run?.resource_references;
|
||||||
|
|
||||||
// TODO(jlyaoyuli): support cloning recurring run with query parameter from isRecurring.
|
const titleVerb = cloneOrigin.isClone ? 'Clone' : 'Start';
|
||||||
const titleVerb = existingRunId ? 'Clone' : 'Start';
|
const titleAdjective = cloneOrigin.isClone ? '' : 'new';
|
||||||
const titleAdjective = existingRunId ? '' : 'new';
|
|
||||||
|
|
||||||
// Title and list of actions on the top of page.
|
// Title and list of actions on the top of page.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -165,7 +221,7 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
: `${titleVerb} a ${titleAdjective} run`,
|
: `${titleVerb} a ${titleAdjective} run`,
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, [isRecurringRun]);
|
||||||
|
|
||||||
// Pre-fill names for pipeline, pipeline version and experiment.
|
// Pre-fill names for pipeline, pipeline version and experiment.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -188,12 +244,15 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
if (apiRun?.run?.name) {
|
if (apiRun?.run?.name) {
|
||||||
const cloneRunName = 'Clone of ' + apiRun.run.name;
|
const cloneRunName = 'Clone of ' + apiRun.run.name;
|
||||||
setRunName(cloneRunName);
|
setRunName(cloneRunName);
|
||||||
|
} else if (apiRecurringRun?.name) {
|
||||||
|
const cloneRecurringName = 'Clone of ' + apiRecurringRun.name;
|
||||||
|
setRunName(cloneRecurringName);
|
||||||
} else if (existingPipelineVersion?.name) {
|
} else if (existingPipelineVersion?.name) {
|
||||||
const initRunName =
|
const initRunName =
|
||||||
'Run of ' + existingPipelineVersion.name + ' (' + generateRandomString(5) + ')';
|
'Run of ' + existingPipelineVersion.name + ' (' + generateRandomString(5) + ')';
|
||||||
setRunName(initRunName);
|
setRunName(initRunName);
|
||||||
}
|
}
|
||||||
}, [apiRun, existingPipelineVersion]);
|
}, [apiRun, apiRecurringRun, existingPipelineVersion]);
|
||||||
|
|
||||||
// Set pipeline spec, pipeline root and parameters fields on UI based on returned template.
|
// Set pipeline spec, pipeline root and parameters fields on UI based on returned template.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -225,12 +284,6 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
}
|
}
|
||||||
}, [templateString, errorMessage, isParameterValid, isMaxConcurrentRunValid]);
|
}, [templateString, errorMessage, isParameterValid, isMaxConcurrentRunValid]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (apiRun?.run?.pipeline_spec?.runtime_config) {
|
|
||||||
setClonedRuntimeConfig(apiRun?.run?.pipeline_spec?.runtime_config);
|
|
||||||
}
|
|
||||||
}, [apiRun]);
|
|
||||||
|
|
||||||
// Whenever any input value changes, validate and show error if needed.
|
// Whenever any input value changes, validate and show error if needed.
|
||||||
// TODO(zijianjoy): Validate run name for now, we need to validate others first.
|
// TODO(zijianjoy): Validate run name for now, we need to validate others first.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -264,7 +317,7 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
relationship: ApiRelationship.OWNER,
|
relationship: ApiRelationship.OWNER,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (existingPipelineVersion && hasVersionID(apiRun)) {
|
if (existingPipelineVersion && hasVersionID(cloneOrigin)) {
|
||||||
references.push({
|
references.push({
|
||||||
key: {
|
key: {
|
||||||
id: existingPipelineVersion.id,
|
id: existingPipelineVersion.id,
|
||||||
|
|
@ -279,7 +332,7 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
name: runName,
|
name: runName,
|
||||||
pipeline_spec: {
|
pipeline_spec: {
|
||||||
// FE can only provide either pipeline_manifest or pipeline version
|
// FE can only provide either pipeline_manifest or pipeline version
|
||||||
pipeline_manifest: hasVersionID(apiRun) ? undefined : templateString,
|
pipeline_manifest: hasVersionID(cloneOrigin) ? undefined : templateString,
|
||||||
runtime_config: {
|
runtime_config: {
|
||||||
// TODO(zijianjoy): determine whether to provide pipeline root.
|
// TODO(zijianjoy): determine whether to provide pipeline root.
|
||||||
pipeline_root: undefined, // pipelineRoot,
|
pipeline_root: undefined, // pipelineRoot,
|
||||||
|
|
@ -287,7 +340,7 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
//TODO(jlyaoyuli): deprecate the resource reference and use pipeline / workflow manifest
|
//TODO(jlyaoyuli): deprecate the resource reference and use pipeline / workflow manifest
|
||||||
resource_references: apiResourceRefFromRun ? apiResourceRefFromRun : references,
|
resource_references: existResourceRef ? existResourceRef : references,
|
||||||
service_account: serviceAccount,
|
service_account: serviceAccount,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -297,7 +350,7 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
? {
|
? {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
max_concurrency: maxConcurrentRuns || '1',
|
max_concurrency: maxConcurrentRuns || '1',
|
||||||
no_catchup: !catchup,
|
no_catchup: !needCatchup,
|
||||||
trigger: trigger,
|
trigger: trigger,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
|
@ -371,14 +424,23 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
<div className={commonCss.scrollContainer}>
|
<div className={commonCss.scrollContainer}>
|
||||||
<div className={commonCss.header}>Run details</div>
|
<div className={commonCss.header}>Run details</div>
|
||||||
|
|
||||||
{apiRun && (
|
{cloneOrigin.isClone && (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<span>{usePipelineFromRunLabel}</span>
|
<span>{usePipelineFromRunLabel}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes(padding(10, 't'))}>
|
<div className={classes(padding(10, 't'))}>
|
||||||
|
{/* TODO(jlyaoyuli): View pipelineDetails from existing recurring run*/}
|
||||||
{apiRun && (
|
{apiRun && (
|
||||||
<Link className={classes(commonCss.link)} to={pipelineDetailsUrl}>
|
<Link
|
||||||
|
className={classes(commonCss.link)}
|
||||||
|
to={getPipelineDetailsUrl(
|
||||||
|
props,
|
||||||
|
cloneOrigin.isRecurring,
|
||||||
|
existingRunId,
|
||||||
|
originalRecurringRunId,
|
||||||
|
)}
|
||||||
|
>
|
||||||
[View pipeline]
|
[View pipeline]
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
@ -386,7 +448,7 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!apiRun && (
|
{!cloneOrigin.isClone && (
|
||||||
<div>
|
<div>
|
||||||
{/* Pipeline selection */}
|
{/* Pipeline selection */}
|
||||||
<PipelineSelector
|
<PipelineSelector
|
||||||
|
|
@ -508,6 +570,8 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
{/* One-off/Recurring Run Type */}
|
{/* One-off/Recurring Run Type */}
|
||||||
{/* TODO(zijianjoy): Support Recurring Run */}
|
{/* TODO(zijianjoy): Support Recurring Run */}
|
||||||
<div className={commonCss.header}>Run Type</div>
|
<div className={commonCss.header}>Run Type</div>
|
||||||
|
{cloneOrigin.isClone === true && <span>{isRecurringRun ? 'Recurring' : 'One-off'}</span>}
|
||||||
|
{cloneOrigin.isClone === false && (
|
||||||
<>
|
<>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
id='oneOffToggle'
|
id='oneOffToggle'
|
||||||
|
|
@ -524,6 +588,7 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
checked={isRecurringRun}
|
checked={isRecurringRun}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Recurring run controls */}
|
{/* Recurring run controls */}
|
||||||
{isRecurringRun && (
|
{isRecurringRun && (
|
||||||
|
|
@ -535,7 +600,7 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
initialProps={{
|
initialProps={{
|
||||||
trigger: trigger,
|
trigger: trigger,
|
||||||
maxConcurrentRuns: maxConcurrentRuns,
|
maxConcurrentRuns: maxConcurrentRuns,
|
||||||
catchup: catchup,
|
catchup: needCatchup,
|
||||||
}}
|
}}
|
||||||
onChange={({ trigger, maxConcurrentRuns, catchup }) => {
|
onChange={({ trigger, maxConcurrentRuns, catchup }) => {
|
||||||
setTrigger(trigger);
|
setTrigger(trigger);
|
||||||
|
|
@ -543,7 +608,7 @@ function NewRunV2(props: NewRunV2Props) {
|
||||||
setIsMaxConcurrentRunValid(
|
setIsMaxConcurrentRunValid(
|
||||||
Number.isInteger(Number(maxConcurrentRuns)) && Number(maxConcurrentRuns) > 0,
|
Number.isInteger(Number(maxConcurrentRuns)) && Number(maxConcurrentRuns) > 0,
|
||||||
);
|
);
|
||||||
setCatchup(catchup);
|
setNeedCatchup(catchup);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue