diff --git a/frontend/src/__tests__/cypress/cypress/pages/pipelines/pipelineVersionImportModal.ts b/frontend/src/__tests__/cypress/cypress/pages/pipelines/pipelineVersionImportModal.ts index cb021172b3..b4e45ac77d 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/pipelines/pipelineVersionImportModal.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/pipelines/pipelineVersionImportModal.ts @@ -14,7 +14,7 @@ class PipelineImportModal extends Modal { } find() { - return cy.findByTestId('upload-version-modal').parents('div[role="dialog"]'); + return cy.findByTestId('import-pipeline-modal').parents('div[role="dialog"]'); } findSubmitButton() { @@ -22,11 +22,11 @@ class PipelineImportModal extends Modal { } findVersionNameInput() { - return this.find().findByTestId('pipeline-version-name'); + return this.find().findByTestId('pipeline-name'); } findVersionDescriptionInput() { - return this.find().findByTestId('pipeline-version-description'); + return this.find().findByTestId('pipeline-description'); } findUploadPipelineInput() { diff --git a/frontend/src/concepts/pipelines/content/compareRuns/CompareRunTableToolbar.tsx b/frontend/src/concepts/pipelines/content/compareRuns/CompareRunTableToolbar.tsx index 9d9ccb40ea..970fd0712d 100644 --- a/frontend/src/concepts/pipelines/content/compareRuns/CompareRunTableToolbar.tsx +++ b/frontend/src/concepts/pipelines/content/compareRuns/CompareRunTableToolbar.tsx @@ -1,35 +1,17 @@ import * as React from 'react'; -import { Button, TextInput, ToolbarItem } from '@patternfly/react-core'; +import { Button, ToolbarItem } from '@patternfly/react-core'; import { useNavigate, useParams } from 'react-router'; -import PipelineFilterBar from '~/concepts/pipelines/content/tables/PipelineFilterBar'; -import SimpleSelect from '~/components/SimpleSelect'; import { FilterOptions } from '~/concepts/pipelines/content/tables/usePipelineFilter'; -import ExperimentSearchInput from '~/concepts/pipelines/content/tables/ExperimentSearchInput'; -import { RuntimeStateKF, runtimeStateLabels } from '~/concepts/pipelines/kfTypes'; -import DashboardDatePicker from '~/components/DashboardDatePicker'; -import PipelineVersionSelect from '~/concepts/pipelines/content/pipelineSelector/CustomPipelineVersionSelect'; -import { PipelineRunVersionsContext } from '~/pages/pipelines/global/runs/PipelineRunVersionsContext'; import { experimentsBaseRoute, experimentsManageCompareRunsRoute } from '~/routes'; import { useCompareRuns } from '~/concepts/pipelines/content/compareRuns/CompareRunsContext'; - -export type FilterProps = Pick< - React.ComponentProps, - 'filterData' | 'onFilterUpdate' ->; +import PipelineRunTableToolbarBase, { + FilterProps, +} from '~/concepts/pipelines/content/tables/pipelineRun/PipelineRunTableToolbarBase'; const CompareRunTableToolbar: React.FC = ({ ...toolbarProps }) => { - const { versions } = React.useContext(PipelineRunVersionsContext); const { runs } = useCompareRuns(); const navigate = useNavigate(); const { namespace, experimentId } = useParams(); - /* eslint-disable @typescript-eslint/no-unused-vars */ - const { - [RuntimeStateKF.RUNTIME_STATE_UNSPECIFIED]: unspecifiedState, - [RuntimeStateKF.PAUSED]: pausedState, - [RuntimeStateKF.CANCELED]: cancelledState, - ...statusRuntimeStates - } = runtimeStateLabels; - /* eslint-enable @typescript-eslint/no-unused-vars */ const options = React.useMemo( () => ({ @@ -48,60 +30,7 @@ const CompareRunTableToolbar: React.FC = ({ ...toolbarProps }) => { } return ( - ( - onChange(value)} - /> - ), - [FilterOptions.EXPERIMENT]: ({ onChange, value, label }) => ( - onChange(data?.value, data?.label)} - selected={value && label ? { value, label } : undefined} - /> - ), - [FilterOptions.PIPELINE_VERSION]: ({ onChange, label }) => ( - onChange(version.pipeline_version_id, version.display_name)} - /> - ), - [FilterOptions.CREATED_AT]: ({ onChange, ...props }) => ( - { - if (date || !value) { - onChange(value); - } - }} - /> - ), - [FilterOptions.STATUS]: ({ value, onChange, ...props }) => ( - ({ - key: v, - label: v, - }))} - toggleLabel={value} - onChange={(selection) => onChange(selection)} - dataTestId="runtime-status-dropdown" - /> - ), - }} - > + - + ); }; diff --git a/frontend/src/concepts/pipelines/content/experiments/ExperimentLoadingError.tsx b/frontend/src/concepts/pipelines/content/experiments/ExperimentLoadingError.tsx new file mode 100644 index 0000000000..84c0c7bc90 --- /dev/null +++ b/frontend/src/concepts/pipelines/content/experiments/ExperimentLoadingError.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { + Bullseye, + EmptyState, + EmptyStateBody, + EmptyStateHeader, + EmptyStateIcon, +} from '@patternfly/react-core'; +import { ExclamationCircleIcon } from '@patternfly/react-icons'; + +interface ExperimentLoadingErrorProps { + error: Error; +} + +const ExperimentLoadingError: React.FC = ({ error }) => ( + + + } + headingLevel="h2" + /> + {error.message} + + +); + +export default ExperimentLoadingError; diff --git a/frontend/src/concepts/pipelines/content/import/PipelineImportBase.tsx b/frontend/src/concepts/pipelines/content/import/PipelineImportBase.tsx new file mode 100644 index 0000000000..4335b38ae7 --- /dev/null +++ b/frontend/src/concepts/pipelines/content/import/PipelineImportBase.tsx @@ -0,0 +1,201 @@ +import * as React from 'react'; +import { + Alert, + Button, + Form, + FormGroup, + Modal, + Stack, + StackItem, + TextArea, + TextInput, +} from '@patternfly/react-core'; +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import { PipelineKFv2, PipelineVersionKFv2 } from '~/concepts/pipelines/kfTypes'; +import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils'; +import { DuplicateNameHelperText } from '~/concepts/pipelines/content/DuplicateNameHelperText'; +import { + PIPELINE_IMPORT_ARGO_ERROR_TEXT, + PIPELINE_ARGO_ERROR, +} from '~/concepts/pipelines/content/const'; +import { UpdateObjectAtPropAndValue } from '~/pages/projects/types'; +import useDebounceCallback from '~/utilities/useDebounceCallback'; +import { PipelineUploadOption, extractKindFromPipelineYAML } from './utils'; +import PipelineUploadRadio from './PipelineUploadRadio'; +import { PipelineImportData } from './useImportModalData'; + +export type PipelineImportBaseProps = { + title: string; + submitButtonText: string; + onClose: ( + result?: PipelineKFv2 | PipelineVersionKFv2, + currentPipeline?: PipelineKFv2 | null, + ) => void; + data: PipelineImportData; + setData: UpdateObjectAtPropAndValue; + resetData: () => void; + submitAction: () => Promise; + checkForDuplicateName: (value: string) => Promise; + children?: React.ReactNode; +}; + +const PipelineImportBase: React.FC = ({ + title, + submitButtonText, + onClose, + data, + setData, + resetData, + submitAction, + checkForDuplicateName, + children, +}) => { + const { project, apiAvailable } = usePipelinesAPI(); + const [importing, setImporting] = React.useState(false); + const [error, setError] = React.useState(); + const { name, description, fileContents, pipelineUrl, uploadOption } = data; + const [hasDuplicateName, setHasDuplicateName] = React.useState(false); + const isArgoWorkflow = extractKindFromPipelineYAML(fileContents) === 'Workflow'; + + const isImportButtonDisabled = + !apiAvailable || + importing || + !name || + (uploadOption === PipelineUploadOption.URL_IMPORT ? !pipelineUrl : !fileContents); + + const onBeforeClose = React.useCallback( + (result?: PipelineKFv2 | PipelineVersionKFv2) => { + onClose(result, data.pipeline); + setImporting(false); + setError(undefined); + resetData(); + setHasDuplicateName(false); + }, + [onClose, resetData, data.pipeline], + ); + + const debouncedCheckForDuplicateName = useDebounceCallback( + React.useCallback( + async (value: string) => { + const isDuplicate = await checkForDuplicateName(value); + setHasDuplicateName(!!isDuplicate); + }, + [checkForDuplicateName], + ), + 500, + ); + + const handleNameChange = React.useCallback( + async (value: string) => { + setHasDuplicateName(false); + setData('name', value); + debouncedCheckForDuplicateName(value); + }, + [debouncedCheckForDuplicateName, setData], + ); + + const onSubmit = () => { + setImporting(true); + setError(undefined); + + if (uploadOption === PipelineUploadOption.FILE_UPLOAD && isArgoWorkflow) { + setImporting(false); + setError(new Error(PIPELINE_IMPORT_ARGO_ERROR_TEXT)); + } else { + submitAction() + .then((result) => { + onBeforeClose(result); + }) + .catch((e) => { + setImporting(false); + setError(e); + }); + } + }; + + return ( + onBeforeClose()} + actions={[ + , + , + ]} + variant="medium" + data-testid="import-pipeline-modal" + > +
+ + + + {getDisplayNameFromK8sResource(project)} + + + {children} + + + handleNameChange(value)} + /> + {hasDuplicateName && } + + + + +