From 088bf639fe17625b8ab204cd4cd6bc52ed65b6c4 Mon Sep 17 00:00:00 2001 From: Juntao Wang Date: Mon, 14 Aug 2023 14:39:53 -0400 Subject: [PATCH 01/14] Elevate quick starts context to the whole application layer --- frontend/src/app/App.tsx | 5 ++++- .../enabledApplications/EnabledApplications.tsx | 9 +-------- .../src/pages/learningCenter/LearningCenter.tsx | 9 +-------- .../pages/notebookController/NotebookController.tsx | 13 +++++-------- 4 files changed, 11 insertions(+), 25 deletions(-) diff --git a/frontend/src/app/App.tsx b/frontend/src/app/App.tsx index 89da2dde65..c42d128ec2 100644 --- a/frontend/src/app/App.tsx +++ b/frontend/src/app/App.tsx @@ -26,6 +26,7 @@ import { AppContext } from './AppContext'; import { useApplicationSettings } from './useApplicationSettings'; import TelemetrySetup from './TelemetrySetup'; import { logout } from './appUtils'; +import QuickStarts from './QuickStarts'; import './App.scss'; @@ -99,7 +100,9 @@ const App: React.FC = () => { > - + + + diff --git a/frontend/src/pages/enabledApplications/EnabledApplications.tsx b/frontend/src/pages/enabledApplications/EnabledApplications.tsx index e112137016..8114c42d3b 100644 --- a/frontend/src/pages/enabledApplications/EnabledApplications.tsx +++ b/frontend/src/pages/enabledApplications/EnabledApplications.tsx @@ -5,7 +5,6 @@ import { useWatchComponents } from '~/utilities/useWatchComponents'; import { OdhApplication } from '~/types'; import ApplicationsPage from '~/pages/ApplicationsPage'; import OdhAppCard from '~/components/OdhAppCard'; -import QuickStarts from '~/app/QuickStarts'; import { fireTrackingEvent } from '~/utilities/segmentIOUtils'; const description = `Launch your enabled applications, view documentation, or get started with quick start instructions and tasks.`; @@ -74,13 +73,7 @@ const EnabledApplications: React.FC = () => { }, [components, loaded]); return ( - - - + ); }; diff --git a/frontend/src/pages/learningCenter/LearningCenter.tsx b/frontend/src/pages/learningCenter/LearningCenter.tsx index 17e5a7a8b9..5a778ccaaf 100644 --- a/frontend/src/pages/learningCenter/LearningCenter.tsx +++ b/frontend/src/pages/learningCenter/LearningCenter.tsx @@ -9,7 +9,6 @@ import { useWatchDocs } from '~/utilities/useWatchDocs'; import { useBrowserStorage } from '~/components/browserStorage'; import { useQueryParams } from '~/utilities/useQueryParams'; import ApplicationsPage from '~/pages/ApplicationsPage'; -import QuickStarts from '~/app/QuickStarts'; import { DOC_LINK, ODH_PRODUCT_NAME } from '~/utilities/const'; import { combineCategoryAnnotations } from '~/utilities/utils'; import { useDeepCompareMemoize } from '~/utilities/useDeepCompareMemoize'; @@ -230,10 +229,4 @@ export const LearningCenter: React.FC = () => { ); }; -const LearningCenterWrapper: React.FC = () => ( - - - -); - -export default LearningCenterWrapper; +export default LearningCenter; diff --git a/frontend/src/pages/notebookController/NotebookController.tsx b/frontend/src/pages/notebookController/NotebookController.tsx index ef388b5325..8d34768031 100644 --- a/frontend/src/pages/notebookController/NotebookController.tsx +++ b/frontend/src/pages/notebookController/NotebookController.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import QuickStarts from '~/app/QuickStarts'; import { useUser } from '~/redux/selectors'; import NotebookServerRoutes from './screens/server/NotebookServerRoutes'; import NotebookControllerTabs from './screens/admin/NotebookControllerTabs'; @@ -10,13 +9,11 @@ const NotebookController: React.FC = () => { const { isAdmin } = useUser(); return ( - - - - {isAdmin ? : } - - - + + + {isAdmin ? : } + + ); }; From 8b97fdf8414c5beabb29d48b46d22272e710856f Mon Sep 17 00:00:00 2001 From: Alex Creasy Date: Thu, 3 Aug 2023 17:31:29 +0100 Subject: [PATCH 02/14] Fixes: Workbench not editable for a little while after it's stopped (#1551) --- .../notebook/NotebookStatusToggle.tsx | 14 ++++++------- .../src/pages/projects/notebook/service.ts | 2 ++ frontend/src/pages/projects/notebook/types.ts | 2 ++ ... => useRefreshNotebookUntilStartOrStop.ts} | 20 ++++++++++++------- .../detail/notebooks/NotebookTableRow.tsx | 2 +- 5 files changed, 25 insertions(+), 15 deletions(-) rename frontend/src/pages/projects/notebook/{useRefreshNotebookUntilStart.ts => useRefreshNotebookUntilStartOrStop.ts} (59%) diff --git a/frontend/src/pages/projects/notebook/NotebookStatusToggle.tsx b/frontend/src/pages/projects/notebook/NotebookStatusToggle.tsx index 83b54db070..cc63f9c3a6 100644 --- a/frontend/src/pages/projects/notebook/NotebookStatusToggle.tsx +++ b/frontend/src/pages/projects/notebook/NotebookStatusToggle.tsx @@ -8,7 +8,7 @@ import { computeNotebooksTolerations } from '~/utilities/tolerations'; import { useAppContext } from '~/app/AppContext'; import { currentlyHasPipelines } from '~/concepts/pipelines/elyra/utils'; import { NotebookState } from './types'; -import useRefreshNotebookUntilStart from './useRefreshNotebookUntilStart'; +import useRefreshNotebookUntilStartOrStop from './useRefreshNotebookUntilStartOrStop'; import StopNotebookConfirmModal from './StopNotebookConfirmModal'; import useStopNotebookModalAvailability from './useStopNotebookModalAvailability'; import NotebookStatusText from './NotebookStatusText'; @@ -24,12 +24,12 @@ const NotebookStatusToggle: React.FC = ({ doListen, enablePipelines, }) => { - const { notebook, isStarting, isRunning, refresh } = notebookState; + const { notebook, isStarting, isRunning, isStopping, refresh } = notebookState; const gpuNumber = useNotebookGPUNumber(notebook); const { size } = useNotebookDeploymentSize(notebook); const [isOpenConfirm, setOpenConfirm] = React.useState(false); const [inProgress, setInProgress] = React.useState(false); - const listenToNotebookStart = useRefreshNotebookUntilStart(notebookState, doListen); + const listenToNotebookStart = useRefreshNotebookUntilStartOrStop(notebookState, doListen); const [dontShowModalValue] = useStopNotebookModalAvailability(); const { dashboardConfig } = useAppContext(); const notebookName = notebook.metadata.name; @@ -42,8 +42,8 @@ const NotebookStatusToggle: React.FC = ({ let label = ''; if (isStarting) { label = 'Starting...'; - } else if (inProgress) { - label = isChecked ? 'Starting...' : 'Stopping...'; + } else if (isStopping) { + label = 'Stopping...'; } else { label = isRunning ? 'Running' : 'Stopped'; } @@ -72,7 +72,7 @@ const NotebookStatusToggle: React.FC = ({ setInProgress(true); stopNotebook(notebookName, notebookNamespace).then(() => { refresh().then(() => setInProgress(false)); - listenToNotebookStart(false); + listenToNotebookStart(true, true); }); }, [notebookName, notebookNamespace, refresh, listenToNotebookStart, fireNotebookTrackingEvent]); @@ -82,7 +82,7 @@ const NotebookStatusToggle: React.FC = ({ { diff --git a/frontend/src/pages/projects/notebook/service.ts b/frontend/src/pages/projects/notebook/service.ts index 550b087a49..c7bb080223 100644 --- a/frontend/src/pages/projects/notebook/service.ts +++ b/frontend/src/pages/projects/notebook/service.ts @@ -34,6 +34,8 @@ export const getNotebooksStatus = async ( notebook: notebooks[i], isStarting: !isStopped && !podsReady, isRunning: !isStopped && podsReady, + isStopping: isStopped && podsReady, + isStopped: isStopped && !podsReady, runningPodUid: pods[0]?.metadata?.uid || '', }, ]; diff --git a/frontend/src/pages/projects/notebook/types.ts b/frontend/src/pages/projects/notebook/types.ts index 5046993f6b..d8d889905d 100644 --- a/frontend/src/pages/projects/notebook/types.ts +++ b/frontend/src/pages/projects/notebook/types.ts @@ -5,6 +5,8 @@ export type NotebookDataState = { notebook: NotebookKind; isStarting: boolean; isRunning: boolean; + isStopping: boolean; + isStopped: boolean; runningPodUid: string; }; diff --git a/frontend/src/pages/projects/notebook/useRefreshNotebookUntilStart.ts b/frontend/src/pages/projects/notebook/useRefreshNotebookUntilStartOrStop.ts similarity index 59% rename from frontend/src/pages/projects/notebook/useRefreshNotebookUntilStart.ts rename to frontend/src/pages/projects/notebook/useRefreshNotebookUntilStartOrStop.ts index a005745447..7d4f9455ab 100644 --- a/frontend/src/pages/projects/notebook/useRefreshNotebookUntilStart.ts +++ b/frontend/src/pages/projects/notebook/useRefreshNotebookUntilStartOrStop.ts @@ -2,11 +2,12 @@ import * as React from 'react'; import { FAST_POLL_INTERVAL } from '~/utilities/const'; import { NotebookState } from './types'; -const useRefreshNotebookUntilStart = ( +const useRefreshNotebookUntilStartOrStop = ( notebookState: NotebookState, doListen: boolean, -): ((listen: boolean) => void) => { +): ((listen: boolean, stop?: boolean) => void) => { const [watchingForNotebook, setWatchingForNotebook] = React.useState(false); + const [watchingForStop, setWatchingForStop] = React.useState(false); const lastNotebookState = React.useRef(notebookState); lastNotebookState.current = notebookState; @@ -14,8 +15,9 @@ const useRefreshNotebookUntilStart = ( let interval: ReturnType; if (watchingForNotebook && doListen) { interval = setInterval(() => { - const { isRunning, refresh } = lastNotebookState.current; - if (!isRunning) { + const { isRunning, isStopped, refresh } = lastNotebookState.current; + const condition = watchingForStop ? isStopped : isRunning; + if (!condition) { refresh().catch((e) => { /* eslint-disable-next-line no-console */ console.error('Error refreshing, stopping notebook refresh', e); @@ -30,11 +32,15 @@ const useRefreshNotebookUntilStart = ( return () => { clearInterval(interval); }; - }, [watchingForNotebook, doListen]); + }, [watchingForStop, watchingForNotebook, doListen]); - return React.useCallback((listen: boolean) => { + /** + * The second parameter allows listening for the notebook to be stopped. Default is to wait until started. + */ + return React.useCallback((listen: boolean, waitForStop = false) => { + setWatchingForStop(waitForStop); setWatchingForNotebook(listen); }, []); }; -export default useRefreshNotebookUntilStart; +export default useRefreshNotebookUntilStartOrStop; diff --git a/frontend/src/pages/projects/screens/detail/notebooks/NotebookTableRow.tsx b/frontend/src/pages/projects/screens/detail/notebooks/NotebookTableRow.tsx index c54bcede43..979a5c1e23 100644 --- a/frontend/src/pages/projects/screens/detail/notebooks/NotebookTableRow.tsx +++ b/frontend/src/pages/projects/screens/detail/notebooks/NotebookTableRow.tsx @@ -94,7 +94,7 @@ const NotebookTableRow: React.FC = ({ { navigate( From 95b9ec1332161c0cfb4f1f2656fa804b3525026b Mon Sep 17 00:00:00 2001 From: Christian Vogt Date: Mon, 21 Aug 2023 11:44:15 -0400 Subject: [PATCH 03/14] remove unused files --- frontend/src/components/FormGroupSettings.tsx | 68 ------------------- .../DatabaseConnectionInputField.tsx | 35 ---------- .../concepts/secrets/apiHooks/useSecret.ts | 20 ------ .../spawner/storage/useNotebookRootStorage.ts | 28 -------- frontend/src/pages/projects/typeHelpers.ts | 0 frontend/src/utilities/useDebounce.ts | 17 ----- 6 files changed, 168 deletions(-) delete mode 100644 frontend/src/components/FormGroupSettings.tsx delete mode 100644 frontend/src/concepts/pipelines/content/configurePipelinesServer/DatabaseConnectionInputField.tsx delete mode 100644 frontend/src/concepts/secrets/apiHooks/useSecret.ts delete mode 100644 frontend/src/pages/projects/screens/spawner/storage/useNotebookRootStorage.ts delete mode 100644 frontend/src/pages/projects/typeHelpers.ts delete mode 100644 frontend/src/utilities/useDebounce.ts diff --git a/frontend/src/components/FormGroupSettings.tsx b/frontend/src/components/FormGroupSettings.tsx deleted file mode 100644 index 26626614d2..0000000000 --- a/frontend/src/components/FormGroupSettings.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import * as React from 'react'; -import { - FormGroup, - Text, - HelperText, - HelperTextItem, - Alert, - AlertActionCloseButton, - Hint, - HintBody, -} from '@patternfly/react-core'; -import { GroupsConfigField, MenuItemStatus } from '~/pages/groupSettings/groupTypes'; -import { MultiSelection } from './MultiSelection'; - -type FormGroupSettingsProps = { - title: string; - body: string; - groupsField: GroupsConfigField; - items: MenuItemStatus[]; - handleMenuItemSelection: (newState: MenuItemStatus[], field: GroupsConfigField) => void; - handleClose: () => void; - error?: string; -}; - -export const FormGroupSettings: React.FC = ({ - title, - body, - groupsField, - items, - handleMenuItemSelection, - handleClose, - error, -}) => ( - - {body} - handleMenuItemSelection(newState, groupsField)} - /> - {!error && ( - <> - - - {'View, edit, or create groups in OpenShift under User Management'} - - - {groupsField === GroupsConfigField.ADMIN && ( - - - {'All cluster admins are automatically assigned as Data Science administrators.'} - - - )} - - )} - {error && ( - } - > -

{error}

-
- )} -
-); diff --git a/frontend/src/concepts/pipelines/content/configurePipelinesServer/DatabaseConnectionInputField.tsx b/frontend/src/concepts/pipelines/content/configurePipelinesServer/DatabaseConnectionInputField.tsx deleted file mode 100644 index d18421c15b..0000000000 --- a/frontend/src/concepts/pipelines/content/configurePipelinesServer/DatabaseConnectionInputField.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import * as React from 'react'; -import { FormGroup, TextInput } from '@patternfly/react-core'; -import PasswordInput from '~/pages/projects/components/PasswordInput'; -import { DATABASE_CONNECTION_KEYS } from './const'; - -type DatabaseConnectionInputFieldProps = { - isPassword?: boolean; - isRequired: boolean; - onChange: (key: DATABASE_CONNECTION_KEYS, value: string) => void; - type: DATABASE_CONNECTION_KEYS; - value: string; -}; - -const DatabaseConnectionInputField: React.FC = ({ - isPassword, - isRequired, - onChange, - type, - value, -}) => { - const ComponentField = isPassword ? PasswordInput : TextInput; - - return ( - - onChange(type, value)} - /> - - ); -}; - -export default DatabaseConnectionInputField; diff --git a/frontend/src/concepts/secrets/apiHooks/useSecret.ts b/frontend/src/concepts/secrets/apiHooks/useSecret.ts deleted file mode 100644 index a4c0a79175..0000000000 --- a/frontend/src/concepts/secrets/apiHooks/useSecret.ts +++ /dev/null @@ -1,20 +0,0 @@ -import * as React from 'react'; -import useFetchState, { FetchStateCallbackPromise, NotReadyError } from '~/utilities/useFetchState'; -import { getSecret } from '~/api'; -import { SecretKind } from '~/k8sTypes'; - -const useSecret = (name: string | null, namespace: string) => { - const callback = React.useCallback>( - (opts) => { - if (!name) { - return Promise.reject(new NotReadyError('Secret name is missing')); - } - return getSecret(namespace, name, opts); - }, - [name, namespace], - ); - - return useFetchState(callback, null); -}; - -export default useSecret; diff --git a/frontend/src/pages/projects/screens/spawner/storage/useNotebookRootStorage.ts b/frontend/src/pages/projects/screens/spawner/storage/useNotebookRootStorage.ts deleted file mode 100644 index 7770636ced..0000000000 --- a/frontend/src/pages/projects/screens/spawner/storage/useNotebookRootStorage.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as React from 'react'; -import { getPvc } from '~/api'; -import { NotebookKind, PersistentVolumeClaimKind } from '~/k8sTypes'; -import { ROOT_MOUNT_PATH } from '~/pages/projects/pvc/const'; - -const useNotebookRootStorage = (notebook?: NotebookKind): PersistentVolumeClaimKind | undefined => { - const [pvc, setPvc] = React.useState(); - - React.useEffect(() => { - if (notebook) { - const volumeMounts = notebook.spec.template.spec.containers[0].volumeMounts || []; - const volumeMount = volumeMounts.find( - (volumeMount) => volumeMount.mountPath === ROOT_MOUNT_PATH, - ); - if (!volumeMount) { - /* eslint-disable-next-line no-console */ - console.error('No storage mounted on root path'); - setPvc(undefined); - } else { - getPvc(notebook.metadata.namespace, volumeMount.name).then((pvc) => setPvc(pvc)); - } - } - }, [notebook]); - - return pvc; -}; - -export default useNotebookRootStorage; diff --git a/frontend/src/pages/projects/typeHelpers.ts b/frontend/src/pages/projects/typeHelpers.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frontend/src/utilities/useDebounce.ts b/frontend/src/utilities/useDebounce.ts deleted file mode 100644 index cb0f7c0fdc..0000000000 --- a/frontend/src/utilities/useDebounce.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; - -function useDebounce(value: T, delay?: number): T { - const [debouncedValue, setDebouncedValue] = React.useState(value); - - React.useEffect(() => { - const timer = setTimeout(() => setDebouncedValue(value), delay || 500); - - return () => { - clearTimeout(timer); - }; - }, [value, delay]); - - return debouncedValue; -} - -export default useDebounce; From ff00d4a13eef9f906c475d59eac0a6f756959a59 Mon Sep 17 00:00:00 2001 From: Christian Vogt Date: Mon, 21 Aug 2023 14:29:59 -0400 Subject: [PATCH 04/14] bundle only the yaml language --- frontend/config/webpack.common.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/config/webpack.common.js b/frontend/config/webpack.common.js index 419040148e..c59b65211c 100644 --- a/frontend/config/webpack.common.js +++ b/frontend/config/webpack.common.js @@ -219,7 +219,9 @@ module.exports = (env) => { }, ], }), - new MonacoWebpackPlugin(), + new MonacoWebpackPlugin({ + languages: ['yaml'], + }), ], resolve: { extensions: ['.js', '.ts', '.tsx', '.jsx'], From f8002e43cea77b6bd7b1549f48c68ee47f104b16 Mon Sep 17 00:00:00 2001 From: pnaik1 Date: Thu, 17 Aug 2023 10:59:11 +0530 Subject: [PATCH 05/14] ArrowHead topology --- .../pipelines/topology/core/TaskEdge.tsx | 46 +++++++++++++++++++ .../pipelines/topology/core/factories.ts | 3 +- 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 frontend/src/concepts/pipelines/topology/core/TaskEdge.tsx diff --git a/frontend/src/concepts/pipelines/topology/core/TaskEdge.tsx b/frontend/src/concepts/pipelines/topology/core/TaskEdge.tsx new file mode 100644 index 0000000000..aa243cdade --- /dev/null +++ b/frontend/src/concepts/pipelines/topology/core/TaskEdge.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/react-styles/css/components/Topology/topology-components'; +import { + observer, + Edge, + integralShapePath, + DEFAULT_SPACER_NODE_TYPE, + ConnectorArrow, +} from '@patternfly/react-topology'; +interface TaskEdgeProps { + element: Edge; + className?: string; + nodeSeparation?: number; +} + +const TaskEdge: React.FunctionComponent = ({ + element, + className, + nodeSeparation, +}) => { + const startPoint = element.getStartPoint(); + const endPoint = element.getEndPoint(); + const groupClassName = css(styles.topologyEdge, className); + const startIndent: number = element.getData()?.indent || 0; + + return ( + + + + {element.getTarget().getType() !== DEFAULT_SPACER_NODE_TYPE ? ( + + ) : null} + + ); +}; + +export default observer(TaskEdge); diff --git a/frontend/src/concepts/pipelines/topology/core/factories.ts b/frontend/src/concepts/pipelines/topology/core/factories.ts index 90f4aca898..2156503a37 100644 --- a/frontend/src/concepts/pipelines/topology/core/factories.ts +++ b/frontend/src/concepts/pipelines/topology/core/factories.ts @@ -6,12 +6,11 @@ import { GraphComponent, ModelKind, SpacerNode, - TaskEdge, withPanZoom, withSelection, } from '@patternfly/react-topology'; import StandardTaskNode from '~/concepts/pipelines/topology/core/customNodes/StandardTaskNode'; - +import TaskEdge from './TaskEdge'; // Topology gap... their types have issues with Strict TS mode // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore From 7f4fcf919e6610a54a705e9b54b99ff1ead88a05 Mon Sep 17 00:00:00 2001 From: Juntao Wang Date: Mon, 14 Aug 2023 13:59:37 -0400 Subject: [PATCH 06/14] Make notebook controller access button to link behavior --- .../screens/server/NotebookServer.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/notebookController/screens/server/NotebookServer.tsx b/frontend/src/pages/notebookController/screens/server/NotebookServer.tsx index a321129d8e..62989d220c 100644 --- a/frontend/src/pages/notebookController/screens/server/NotebookServer.tsx +++ b/frontend/src/pages/notebookController/screens/server/NotebookServer.tsx @@ -34,6 +34,8 @@ export const NotebookServer: React.FC = () => { [requestNotebookRefresh, navigate], ); + const link = notebook?.metadata.annotations?.['opendatahub.io/link'] || '#'; + return ( <> @@ -54,10 +56,9 @@ export const NotebookServer: React.FC = () => { /> { - if (notebook.metadata.annotations?.['opendatahub.io/link']) { - window.location.href = notebook.metadata.annotations['opendatahub.io/link']; - } else { + onClick={(e) => { + if (link === '#') { + e.preventDefault(); notification.error( 'Error accessing notebook server', 'Failed to redirect page due to missing notebook URL, please try to refresh the page and try it again.', @@ -65,7 +66,7 @@ export const NotebookServer: React.FC = () => { } }} > - From 4d0bce286a02f1465161eeac232238f15d26fb70 Mon Sep 17 00:00:00 2001 From: Juntao Wang Date: Tue, 15 Aug 2023 14:54:49 -0400 Subject: [PATCH 07/14] Update resource name, display name and description when duplicating custom serving runtime --- .../CustomServingRuntimeAddTemplate.tsx | 41 +++++++++++++++---- .../CustomServingRuntimeTableRow.tsx | 2 +- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeAddTemplate.tsx b/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeAddTemplate.tsx index bb42ff04a0..47fac284d0 100644 --- a/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeAddTemplate.tsx +++ b/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeAddTemplate.tsx @@ -21,7 +21,10 @@ import { createServingRuntimeTemplateBackend, updateServingRuntimeTemplateBackend, } from '~/services/templateService'; -import { getServingRuntimeDisplayNameFromTemplate } from './utils'; +import { + getServingRuntimeDisplayNameFromTemplate, + getServingRuntimeNameFromTemplate, +} from './utils'; import { CustomServingRuntimeContext } from './CustomServingRuntimeContext'; type CustomServingRuntimeAddTemplateProps = { @@ -33,13 +36,35 @@ const CustomServingRuntimeAddTemplate: React.FC { const { dashboardNamespace } = useDashboardNamespace(); const { refreshData } = React.useContext(CustomServingRuntimeContext); - const { state } = useLocation(); + const { state }: { state?: { template: TemplateKind } } = useLocation(); + + const copiedServingRuntimeString = React.useMemo( + () => + state + ? YAML.stringify({ + ...state.template.objects[0], + metadata: { + ...state.template.objects[0].metadata, + name: `${getServingRuntimeNameFromTemplate(state.template)}-copy`, + annotations: { + ...state.template.objects[0].metadata.annotations, + 'openshift.io/display-name': `Copy of ${getServingRuntimeDisplayNameFromTemplate( + state.template, + )}`, + 'openshift.io/description': + state.template.objects[0].metadata.annotations?.['openshift.io/description'], + }, + }, + }) + : '', + [state], + ); - const stringifiedTemplate = existingTemplate - ? YAML.stringify(existingTemplate.objects[0]) - : state - ? YAML.stringify(state.template) - : ''; + const stringifiedTemplate = React.useMemo( + () => + existingTemplate ? YAML.stringify(existingTemplate.objects[0]) : copiedServingRuntimeString, + [copiedServingRuntimeString, existingTemplate], + ); const [code, setCode] = React.useState(stringifiedTemplate); const [loading, setIsLoading] = React.useState(false); const [error, setError] = React.useState(undefined); @@ -108,7 +133,7 @@ const CustomServingRuntimeAddTemplate: React.FC