diff --git a/web/src/cde/components/CreateGitspace/CreateGitspace.tsx b/web/src/cde/components/CreateGitspace/CreateGitspace.tsx index 69d8c7fe62..8d5705a49f 100644 --- a/web/src/cde/components/CreateGitspace/CreateGitspace.tsx +++ b/web/src/cde/components/CreateGitspace/CreateGitspace.tsx @@ -33,7 +33,7 @@ import { SelectInfraProvider } from './components/SelectInfraProvider/SelectInfr import css from './CreateGitspace.module.scss' const initData = { - ide: IDEType.VSCODE + ide: IDEType.VSCODEWEB } const GitspaceForm = () => { @@ -78,7 +78,7 @@ const GitspaceForm = () => { ? `${getString('cde.editGitspace')} ${gitspaceData?.config?.name}` : getString('cde.createGitspace')} - + onSubmit={async data => { try { if (gitspaceId) { @@ -101,18 +101,18 @@ const GitspaceForm = () => { formLoading={loadingGitspace || updatingGitspace} enableReinitialize formName={'createGitSpace'} - initialValues={formInitialData} + initialValues={{ ...formInitialData, validated: false }} validateOnMount={false} validationSchema={yup.object().shape({ - branch: yup.string().trim().required(), - code_repo_type: yup.string().trim().required(), - code_repo_url: yup.string().trim().required(), + branch: yup.string().trim().required(getString('cde.branchValidationMessage')), + code_repo_type: yup.string().trim().required(getString('cde.repoValidationMessage')), + code_repo_url: yup.string().trim().required(getString('cde.repoValidationMessage')), id: yup.string().trim().required(), ide: yup.string().trim().required(), - infra_provider_resource_id: yup.string().trim().required(), + infra_provider_resource_id: yup.string().trim().required(getString('cde.machineValidationMessage')), name: yup.string().trim().required(), metadata: yup.object().shape({ - region: yup.string().trim().required() + region: yup.string().trim().required(getString('cde.regionValidationMessage')) }) })}> {_ => { diff --git a/web/src/cde/components/CreateGitspace/components/SelectIDE/SelectIDE.tsx b/web/src/cde/components/CreateGitspace/components/SelectIDE/SelectIDE.tsx index 9b19bef9ea..dc390a4a7c 100644 --- a/web/src/cde/components/CreateGitspace/components/SelectIDE/SelectIDE.tsx +++ b/web/src/cde/components/CreateGitspace/components/SelectIDE/SelectIDE.tsx @@ -42,12 +42,10 @@ export const SelectIDE = () => { - {ide ? getString('cde.ide.ide') : getString('cde.ide.selectIDE')} + {ide ? getString('cde.ide.title') : getString('cde.ide.selectIDE')} {ide && ( - - {`${getString('cde.ide.vsCode')} ${IDELabel}` || getString('cde.ide.ide')} - + {`${IDELabel}` || getString('cde.ide.title')} )} diff --git a/web/src/cde/components/CreateGitspace/components/SelectInfraProvider/SelectInfraProvider.tsx b/web/src/cde/components/CreateGitspace/components/SelectInfraProvider/SelectInfraProvider.tsx index 9efe01ac04..9118cd05d6 100644 --- a/web/src/cde/components/CreateGitspace/components/SelectInfraProvider/SelectInfraProvider.tsx +++ b/web/src/cde/components/CreateGitspace/components/SelectInfraProvider/SelectInfraProvider.tsx @@ -20,17 +20,15 @@ import { Layout } from '@harnessio/uicore' import { useFormikContext } from 'formik' import { useParams } from 'react-router-dom' import { CDEPathParams, useGetCDEAPIParams } from 'cde/hooks/useGetCDEAPIParams' -import { OpenapiCreateGitspaceRequest, useListInfraProviderResources } from 'services/cde' +import { OpenapiCreateGitspaceRequest, useListInfraProviderResourcesForAccount } from 'services/cde' import { SelectRegion } from '../SelectRegion/SelectRegion' import { SelectMachine } from '../SelectMachine/SelectMachine' export const SelectInfraProvider = () => { const { values, setFieldValue: onChange } = useFormikContext() - const { accountIdentifier, orgIdentifier, projectIdentifier } = useGetCDEAPIParams() as CDEPathParams - const { data } = useListInfraProviderResources({ + const { accountIdentifier } = useGetCDEAPIParams() as CDEPathParams + const { data } = useListInfraProviderResourcesForAccount({ accountIdentifier, - orgIdentifier, - projectIdentifier, infraProviderConfigIdentifier: 'HARNESS_GCP' }) @@ -41,7 +39,7 @@ export const SelectInfraProvider = () => { useEffect(() => { if (gitspaceId && values.infra_provider_resource_id && optionsList.length) { const match = optionsList.find(item => item.id === values.infra_provider_resource_id) - if (values?.metadata?.region !== values.infra_provider_resource_id) { + if (values?.metadata?.region !== match?.region) { onChange('metadata.region', match?.region?.toLowerCase()) } } @@ -60,8 +58,8 @@ export const SelectInfraProvider = () => { return ( - - + + ) } diff --git a/web/src/cde/components/CreateGitspace/components/SelectMachine/SelectMachine.tsx b/web/src/cde/components/CreateGitspace/components/SelectMachine/SelectMachine.tsx index 0f6e6425b9..e17613e364 100644 --- a/web/src/cde/components/CreateGitspace/components/SelectMachine/SelectMachine.tsx +++ b/web/src/cde/components/CreateGitspace/components/SelectMachine/SelectMachine.tsx @@ -14,12 +14,13 @@ * limitations under the License. */ -import React from 'react' +import React, { useEffect } from 'react' import { Layout, Text } from '@harnessio/uicore' import { Menu, MenuItem } from '@blueprintjs/core' import { Cpu } from 'iconoir-react' import { useFormikContext } from 'formik' import { FontVariation } from '@harnessio/design-system' +import { useParams } from 'react-router-dom' import { GitspaceSelect } from 'cde/components/GitspaceSelect/GitspaceSelect' import type { OpenapiCreateGitspaceRequest, TypesInfraProviderResourceResponse } from 'services/cde' import { useStrings } from 'framework/strings' @@ -37,12 +38,14 @@ export const labelToMachineId = { interface SelectMachineInterface { options: TypesInfraProviderResourceResponse[] + defaultValue: TypesInfraProviderResourceResponse } -export const SelectMachine = ({ options }: SelectMachineInterface) => { +export const SelectMachine = ({ options, defaultValue }: SelectMachineInterface) => { const { getString } = useStrings() const { values, errors, setFieldValue: onChange } = useFormikContext() const { infra_provider_resource_id: machine } = values + const { gitspaceId = '' } = useParams<{ gitspaceId?: string }>() const machineTypes = options.map(item => { const { cpu, disk, memory, id, name } = item @@ -55,15 +58,24 @@ export const SelectMachine = ({ options }: SelectMachineInterface) => { } }) + useEffect(() => { + if (defaultValue && gitspaceId) { + onChange('infra_provider_resource_id', defaultValue.id) + } + }, [defaultValue?.id, gitspaceId]) + const data = (machineTypes?.find(item => item.id === machine) || {}) as (typeof machineTypes)[0] return ( - - {data.label || getString('cde.machine')} + + + + {getString('cde.machine')} + {data.label || getString('cde.machine')} + } errorMessage={errors.infra_provider_resource_id} diff --git a/web/src/cde/components/CreateGitspace/components/SelectRegion/SelectRegion.tsx b/web/src/cde/components/CreateGitspace/components/SelectRegion/SelectRegion.tsx index 200ff0f6b9..e4ffa4a2bf 100644 --- a/web/src/cde/components/CreateGitspace/components/SelectRegion/SelectRegion.tsx +++ b/web/src/cde/components/CreateGitspace/components/SelectRegion/SelectRegion.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { Layout, Text } from '@harnessio/uicore' import { Menu, MenuItem } from '@blueprintjs/core' import { Map } from 'iconoir-react' @@ -31,6 +31,7 @@ import Empty from './assests/Empty.png' interface SelectRegionInterface { disabled?: boolean + defaultValue: { label: string; value: TypesInfraProviderResourceResponse[] } options: { label: string; value: TypesInfraProviderResourceResponse[] }[] } @@ -49,20 +50,33 @@ export const getMapFromRegion = (region: string) => { } } -export const SelectRegion = ({ options, disabled }: SelectRegionInterface) => { +export const SelectRegion = ({ options, disabled, defaultValue }: SelectRegionInterface) => { const { getString } = useStrings() - const { values, errors, setFieldValue: onChange } = useFormikContext() - const { metadata } = values + const { + values: { metadata }, + errors, + setFieldValue: onChange + } = useFormikContext() const [regionState, setRegionState] = useState(metadata?.region) + useEffect(() => { + if (!regionState && !disabled) { + setRegionState(defaultValue?.label?.toLowerCase()) + onChange('metadata.region', defaultValue?.label?.toLowerCase()) + } + }, [defaultValue?.label?.toLowerCase()]) + return ( - - {metadata?.region || getString('cde.region')} + + + + {getString('cde.region')} + {metadata?.region || getString('cde.region')} + } formikName="metadata.region" @@ -84,6 +98,7 @@ export const SelectRegion = ({ options, disabled }: SelectRegionInterface) => { text={{label.toUpperCase()}} onClick={() => { onChange('metadata.region', label.toLowerCase()) + onChange('infra_provider_resource_id', undefined) }} onMouseOver={(e: React.MouseEvent) => { setRegionState(e.currentTarget.innerText) diff --git a/web/src/cde/components/CreateGitspace/components/SelectRepository/SelectRepository.tsx b/web/src/cde/components/CreateGitspace/components/SelectRepository/SelectRepository.tsx index eea3942ab7..6c586437d4 100644 --- a/web/src/cde/components/CreateGitspace/components/SelectRepository/SelectRepository.tsx +++ b/web/src/cde/components/CreateGitspace/components/SelectRepository/SelectRepository.tsx @@ -27,7 +27,7 @@ import { OpenapiCreateGitspaceRequest, OpenapiGetCodeRepositoryResponse, useGetC import { GitspaceSelect } from 'cde/components/GitspaceSelect/GitspaceSelect' import { useStrings } from 'framework/strings' import { CodeRepoAccessType } from 'cde/constants' -import { getIconByRepoType, getRepoNameFromURL, isValidUrl } from './SelectRepository.utils' +import { getIconByRepoType, getRepoIdFromURL, getRepoNameFromURL, isValidUrl } from './SelectRepository.utils' import css from './SelectRepository.module.scss' const RepositoryText = ({ repoURL }: { repoURL?: string }) => { @@ -75,13 +75,13 @@ const SelectRepositoryCard = ({ className={css.metadataItem} onClick={() => { onChange((prv: any) => { - const repoId = getRepoNameFromURL(data?.url) + const repoId = getRepoIdFromURL(data?.url) return { ...prv, values: { ...prv.values, id: `${repoId}`, - name: `${repoId}`?.toLowerCase(), + name: getRepoNameFromURL(data?.url), code_repo_url: data?.url || '', code_repo_type: data?.repo_type || '', code_repo_id: repoId, @@ -178,7 +178,7 @@ export const SelectRepository = ({ disabled }: { disabled?: boolean }) => { rightElementProps={{ size: 16, className: css.loadingIcon }} rightElement={loading ? 'loading' : undefined} className={css.urlInput} - placeholder="e.g https://github.com/orkohunter/idp" + placeholder="e.g https://github.com/microsoft/vscode-remote-try-python.git" onChange={async event => { const target = event.target as HTMLInputElement await onChange(target.value) diff --git a/web/src/cde/components/CreateGitspace/components/SelectRepository/SelectRepository.utils.tsx b/web/src/cde/components/CreateGitspace/components/SelectRepository/SelectRepository.utils.tsx index 03532eb28d..33b33bbf0c 100644 --- a/web/src/cde/components/CreateGitspace/components/SelectRepository/SelectRepository.utils.tsx +++ b/web/src/cde/components/CreateGitspace/components/SelectRepository/SelectRepository.utils.tsx @@ -15,7 +15,7 @@ export const isValidUrl = (url: string) => { return !!urlPattern.test(url) } -export const getRepoNameFromURL = (repoURL?: string) => { +export const getRepoIdFromURL = (repoURL?: string) => { const repoURLSplit = repoURL?.split('/') return repoURLSplit?.[repoURLSplit?.length - 1] ?.replace(/-/g, '') @@ -24,6 +24,11 @@ export const getRepoNameFromURL = (repoURL?: string) => { ?.toLowerCase() } +export const getRepoNameFromURL = (repoURL?: string) => { + const repoURLSplit = repoURL?.split('/') + return repoURLSplit?.[repoURLSplit?.length - 1] +} + export enum CodeRepoType { Github = 'github', Gitlab = 'gitlab', diff --git a/web/src/cde/components/GitspaceDetails/GitspaceDetails.module.scss b/web/src/cde/components/GitspaceDetails/GitspaceDetails.module.scss index 8daa2ffa8b..16052740f5 100644 --- a/web/src/cde/components/GitspaceDetails/GitspaceDetails.module.scss +++ b/web/src/cde/components/GitspaceDetails/GitspaceDetails.module.scss @@ -24,6 +24,10 @@ border-radius: var(--spacing-small); } +.repository { + cursor: pointer !important; +} + .popover { > div[class*='popover-arrow'] { display: none; diff --git a/web/src/cde/components/GitspaceDetails/GitspaceDetails.module.scss.d.ts b/web/src/cde/components/GitspaceDetails/GitspaceDetails.module.scss.d.ts index 688a52a5aa..952f8fe077 100644 --- a/web/src/cde/components/GitspaceDetails/GitspaceDetails.module.scss.d.ts +++ b/web/src/cde/components/GitspaceDetails/GitspaceDetails.module.scss.d.ts @@ -18,4 +18,5 @@ // This is an auto-generated file export declare const detailsBar: string export declare const popover: string +export declare const repository: string export declare const subText: string diff --git a/web/src/cde/components/GitspaceDetails/GitspaceDetails.tsx b/web/src/cde/components/GitspaceDetails/GitspaceDetails.tsx index 60b7beb12c..fa2a864ebe 100644 --- a/web/src/cde/components/GitspaceDetails/GitspaceDetails.tsx +++ b/web/src/cde/components/GitspaceDetails/GitspaceDetails.tsx @@ -18,9 +18,9 @@ import React, { useState } from 'react' import { Text, Layout, Container, Button, ButtonVariation, PageError, useToaster } from '@harnessio/uicore' import { FontVariation } from '@harnessio/design-system' import type { PopoverProps } from '@harnessio/uicore/dist/components/Popover/Popover' -import { Menu, MenuItem, PopoverPosition } from '@blueprintjs/core' +import { Menu, MenuItem, PopoverInteractionKind, PopoverPosition } from '@blueprintjs/core' import { useHistory, useParams } from 'react-router-dom' -import { Cpu, Circle, GitFork, Repository } from 'iconoir-react' +import { Cpu, Circle, GitFork, Repository, EditPencil, DeleteCircle } from 'iconoir-react' import { isUndefined } from 'lodash-es' import type { GetDataError, MutateMethod, UseGetProps } from 'restful-react' import { @@ -28,52 +28,28 @@ import { type EnumGitspaceStateType, type GitspaceActionPathParams, type OpenapiGetGitspaceResponse, - type OpenapiGitspaceActionRequest + type OpenapiGitspaceActionRequest, + useDeleteGitspace } from 'services/cde' -import { UseStringsReturn, useStrings } from 'framework/strings' +import { useStrings } from 'framework/strings' import { GitspaceActionType, GitspaceStatus, IDEType } from 'cde/constants' import { getErrorMessage } from 'utils/Utils' import { useAppContext } from 'AppContext' import { useGetSpaceParam } from 'hooks/useGetSpaceParam' -import { useQueryParams } from 'hooks/useQueryParams' import { CDEPathParams, useGetCDEAPIParams } from 'cde/hooks/useGetCDEAPIParams' +import { useConfirmAct } from 'hooks/useConfirmAction' import Gitspace from '../../icons/Gitspace.svg?url' -import { getStatusColor } from '../ListGitspaces/ListGitspaces' +import { StartStopButton, getStatusColor } from '../ListGitspaces/ListGitspaces' import { usePolling } from './usePolling' import EventsTimeline from './EventsTimeline/EventsTimeline' import { GitspaceEventType, pollEventsList } from './GitspaceDetails.constants' +import { getGitspaceStatusLabel } from './GitspaceDetails.utils' import css from './GitspaceDetails.module.scss' interface QueryGitspace { gitspaceId?: string } -export const getGitspaceDetailTitle = ({ - getString, - state, - loading, - redirectFrom, - actionError -}: { - getString: UseStringsReturn['getString'] - state?: EnumGitspaceStateType - loading?: boolean - redirectFrom?: string - actionError?: GetDataError | null -}) => { - if (loading) { - return getString('cde.details.fetchingGitspace') - } else if (state === GitspaceStatus.UNKNOWN || (state === GitspaceStatus.STOPPED && !!redirectFrom && !actionError)) { - return getString('cde.details.provisioningGitspace') - } else if (state === GitspaceStatus.STOPPED) { - return getString('cde.details.gitspaceStopped') - } else if (state === GitspaceStatus.RUNNING) { - return getString('cde.details.gitspaceRunning') - } else if (!loading && isUndefined(state)) { - getString('cde.details.noData') - } -} - export const GitspaceDetails = ({ data, error, @@ -81,15 +57,12 @@ export const GitspaceDetails = ({ refetch, mutate, refetchLogs, - mutateLoading, - isfetchingInProgress, - actionError + mutateLoading }: { data?: OpenapiGetGitspaceResponse | null error?: GetDataError | null loading?: boolean mutateLoading?: boolean - isfetchingInProgress?: boolean refetch: ( options?: Partial, 'lazy'>> | undefined ) => Promise @@ -101,12 +74,11 @@ export const GitspaceDetails = ({ const { gitspaceId = '' } = useParams() const { routes } = useAppContext() const space = useGetSpaceParam() - const { showError } = useToaster() + const { showError, showSuccess } = useToaster() const history = useHistory() const { projectIdentifier, orgIdentifier, accountIdentifier } = useGetCDEAPIParams() as CDEPathParams - const { redirectFrom = '' } = useQueryParams<{ redirectFrom?: string }>() - const [startPolling, setstartPolling] = useState(false) + const [startPolling, setstartPolling] = useState(undefined) const { config, state, url } = data || {} @@ -130,9 +102,9 @@ export const GitspaceDetails = ({ (eventData?.[eventData?.length - 1]?.event || '') as unknown as GitspaceEventType ) - const isPolling = usePolling(refetchEventData, { + usePolling(refetchEventData, { pollingInterval: 10000, - startCondition: startPolling || !pollingCondition + startCondition: Boolean(startPolling) || !pollingCondition }) usePolling(refetchLogs!, { @@ -142,6 +114,32 @@ export const GitspaceDetails = ({ stopCondition: pollingCondition }) + const { mutate: deleteGitspace, loading: deleteLoading } = useDeleteGitspace({ + projectIdentifier, + orgIdentifier, + accountIdentifier + }) + + const confirmDelete = useConfirmAct() + + const handleDelete = async (e: React.MouseEvent) => { + confirmDelete({ + title: getString('cde.deleteGitspaceTitle'), + message: getString('cde.deleteGitspaceText', { name: config?.name }), + action: async () => { + try { + e.preventDefault() + e.stopPropagation() + await deleteGitspace(config?.id || '') + showSuccess(getString('cde.deleteSuccess')) + history.push(routes.toCDEGitspaces({ space })) + } catch (exception) { + showError(getErrorMessage(exception)) + } + } + }) + } + return ( @@ -153,9 +151,14 @@ export const GitspaceDetails = ({ icon={loadingEvents || loading || mutateLoading ? 'loading' : undefined} className={css.subText} font={{ variation: FontVariation.CARD_TITLE }}> - {redirectFrom || isPolling || startPolling - ? eventData?.[eventData?.length - 1]?.message || 'Fetching events' - : getGitspaceDetailTitle({ getString, state, loading, redirectFrom, actionError })} + {getGitspaceStatusLabel({ + isPolling: !pollingCondition, + mutateLoading, + startPolling, + state, + getString, + eventData + })} )} @@ -174,10 +177,13 @@ export const GitspaceDetails = ({ {config?.name} - + window.open(config?.code_repo_url, '_blank')}> - {config?.id} + {config?.code_repo_id || config?.code_repo_url} / @@ -195,67 +201,25 @@ export const GitspaceDetails = ({ )} - - {state === GitspaceStatus.UNKNOWN && ( - <> - - - - )} - {state === GitspaceStatus.STOPPED && ( - <> - - - + + {(state === GitspaceStatus.STOPPED || state === GitspaceStatus.ERROR) && ( + )} {state === GitspaceStatus.RUNNING && ( @@ -271,49 +235,81 @@ export const GitspaceDetails = ({ variation={ButtonVariation.PRIMARY}> {openEditorLabel} - )} + + ) diff --git a/web/src/cde/components/GitspaceDetails/GitspaceDetails.utils.ts b/web/src/cde/components/GitspaceDetails/GitspaceDetails.utils.ts new file mode 100644 index 0000000000..5065de9e90 --- /dev/null +++ b/web/src/cde/components/GitspaceDetails/GitspaceDetails.utils.ts @@ -0,0 +1,50 @@ +import { isUndefined } from 'lodash-es' +import type { UseStringsReturn } from 'framework/strings' +import { GitspaceActionType, GitspaceStatus } from 'cde/constants' +import type { EnumGitspaceStateType, OpenapiGetGitspaceEventResponse } from 'services/cde' + +const getTitleWhenNotPolling = ({ + state, + getString +}: { + state?: EnumGitspaceStateType + getString: UseStringsReturn['getString'] +}) => { + if (state === GitspaceStatus.STOPPED) { + return getString('cde.details.gitspaceStopped') + } else if (state === GitspaceStatus.RUNNING) { + return getString('cde.details.gitspaceRunning') + } else if (isUndefined(state)) { + getString('cde.details.noData') + } +} + +export const getGitspaceStatusLabel = ({ + state, + eventData, + isPolling, + startPolling, + getString, + mutateLoading +}: { + state?: EnumGitspaceStateType + eventData: OpenapiGetGitspaceEventResponse[] | null + isPolling: boolean + getString: UseStringsReturn['getString'] + startPolling?: GitspaceActionType + mutateLoading?: boolean +}) => { + if (mutateLoading || Boolean(startPolling) || isPolling) { + if (isPolling && eventData?.[eventData?.length - 1]?.message) { + return eventData?.[eventData?.length - 1]?.message + } else if (startPolling === GitspaceActionType.START) { + return getString('cde.startingGitspace') + } else if (startPolling === GitspaceActionType.STOP) { + return getString('cde.stopingGitspace') + } else { + return getString('cde.details.fetchingGitspace') + } + } else { + return getTitleWhenNotPolling({ getString, state }) + } +} diff --git a/web/src/cde/components/GitspaceSelect/GitspaceSelect.tsx b/web/src/cde/components/GitspaceSelect/GitspaceSelect.tsx index 638a97c8fb..70b4cd81d3 100644 --- a/web/src/cde/components/GitspaceSelect/GitspaceSelect.tsx +++ b/web/src/cde/components/GitspaceSelect/GitspaceSelect.tsx @@ -19,6 +19,7 @@ import cx from 'classnames' import { Menu, PopoverInteractionKind, PopoverPosition } from '@blueprintjs/core' import { Text, Button, Container, ButtonVariation, FormError } from '@harnessio/uicore' import type { IconName } from '@harnessio/icons' +import { useFormikContext } from 'formik' import { useStrings } from 'framework/strings' import css from './GitspaceSelect.module.scss' @@ -47,6 +48,8 @@ export const GitspaceSelect = ({ const buttonRef = useRef(null) const [popoverWidth, setPopoverWidth] = useState(0) + const { touched } = useFormikContext<{ validated?: boolean }>() + const defaultTooltipProps = { tooltip: ( @@ -93,7 +96,7 @@ export const GitspaceSelect = ({ {...addTooltipProps} disabled={disabled} /> - + {touched.validated && } ) } diff --git a/web/src/cde/components/ListGitspaces/ListGitspaces.module.scss b/web/src/cde/components/ListGitspaces/ListGitspaces.module.scss index 17d7e9930d..6f4a6e492c 100644 --- a/web/src/cde/components/ListGitspaces/ListGitspaces.module.scss +++ b/web/src/cde/components/ListGitspaces/ListGitspaces.module.scss @@ -49,3 +49,7 @@ overflow: hidden; text-overflow: ellipsis; } + +.repositoryCell { + width: fit-content !important; +} diff --git a/web/src/cde/components/ListGitspaces/ListGitspaces.module.scss.d.ts b/web/src/cde/components/ListGitspaces/ListGitspaces.module.scss.d.ts index caed88a99b..7b5539a283 100644 --- a/web/src/cde/components/ListGitspaces/ListGitspaces.module.scss.d.ts +++ b/web/src/cde/components/ListGitspaces/ListGitspaces.module.scss.d.ts @@ -19,4 +19,5 @@ export declare const gitspaceUrl: string export declare const listContainer: string export declare const popover: string +export declare const repositoryCell: string export declare const table: string diff --git a/web/src/cde/components/ListGitspaces/ListGitspaces.tsx b/web/src/cde/components/ListGitspaces/ListGitspaces.tsx index 32000ebaca..b5dd62fb36 100644 --- a/web/src/cde/components/ListGitspaces/ListGitspaces.tsx +++ b/web/src/cde/components/ListGitspaces/ListGitspaces.tsx @@ -21,7 +21,6 @@ import type { Renderer, CellProps } from 'react-table' import ReactTimeago from 'react-timeago' import { Circle, - GithubCircle, GitBranch, Cpu, Clock, @@ -31,25 +30,26 @@ import { ModernTv, OpenInBrowser, DeleteCircle, - EditPencil + EditPencil, + ViewColumns2 } from 'iconoir-react' import { Menu, MenuItem, PopoverInteractionKind, Position } from '@blueprintjs/core' import { useHistory } from 'react-router-dom' -import type { MutateMethod } from 'restful-react' import { useGitspaceAction, type EnumGitspaceStateType, type OpenapiGetGitspaceResponse, EnumIDEType, - useDeleteGitspace, - DeleteGitspacePathParams + useDeleteGitspace } from 'services/cde' import { CDEPathParams, useGetCDEAPIParams } from 'cde/hooks/useGetCDEAPIParams' import { GitspaceActionType, GitspaceStatus, IDEType } from 'cde/constants' import { UseStringsReturn, useStrings } from 'framework/strings' import { useAppContext } from 'AppContext' import { getErrorMessage } from 'utils/Utils' +import { useConfirmAct } from 'hooks/useConfirmAction' import VSCode from '../../icons/VSCode.svg?url' +import { getIconByRepoType } from '../CreateGitspace/components/SelectRepository/SelectRepository.utils' import css from './ListGitspaces.module.scss' export const getStatusColor = (status?: EnumGitspaceStateType) => { @@ -59,8 +59,6 @@ export const getStatusColor = (status?: EnumGitspaceStateType) => { case GitspaceStatus.STOPPED: case GitspaceStatus.ERROR: return '#FF0000' - case GitspaceStatus.UNKNOWN: - return '#808080' default: return '#000000' } @@ -112,27 +110,31 @@ export const RenderGitspaceName: Renderer> } export const RenderRepository: Renderer> = ({ row }) => { - const { getString } = useStrings() const details = row.original - const { config, tracked_changes } = details - const { name, branch } = config || {} + const { config } = details + const { name, branch, code_repo_url, code_repo_type } = config || {} return ( - - - + { + e.preventDefault() + e.stopPropagation() + window.open(code_repo_url, '_blank') + }}> + {getIconByRepoType({ repoType: code_repo_type })} + {name} - : + : - + {branch} - - {tracked_changes || getString('cde.noChange')} - ) } @@ -171,7 +173,7 @@ export const RenderLastActivity: Renderer> ) } -const StartStopButton = ({ state, loading }: { state?: EnumGitspaceStateType; loading?: boolean }) => { +export const StartStopButton = ({ state, loading }: { state?: EnumGitspaceStateType; loading?: boolean }) => { const { getString } = useStrings() return ( @@ -185,7 +187,7 @@ const StartStopButton = ({ state, loading }: { state?: EnumGitspaceStateType; lo ) } -const OpenGitspaceButton = ({ ide }: { ide?: EnumIDEType }) => { +export const OpenGitspaceButton = ({ ide }: { ide?: EnumIDEType }) => { const { getString } = useStrings() return ( @@ -203,7 +205,7 @@ interface ActionMenuProps { loading?: boolean actionLoading?: boolean deleteLoading?: boolean - deleteGitspace: MutateMethod + deleteGitspace: (e: React.MouseEvent) => Promise } const ActionMenu = ({ @@ -221,6 +223,7 @@ const ActionMenu = ({ const { routes } = useAppContext() const pathparamsList = config?.space_path?.split('/') || [] const projectIdentifier = pathparamsList[pathparamsList.length - 1] || '' + return ( { - try { - if (!actionLoading) { - e.preventDefault() - e.stopPropagation() - await handleStartStop() - await refreshList() - } - } catch (error) { - showError(getErrorMessage(error)) - } - }} - text={ - - - - } - /> - { - try { - e.preventDefault() - e.stopPropagation() - await deleteGitspace(config?.id || '') - await refreshList() - } catch (error) { - showError(getErrorMessage(error)) - } + onClick={() => { + history.push( + routes.toCDEGitspaceDetail({ + space: config?.space_path || '', + gitspaceId: config?.id || '' + }) + ) }} text={ - {deleteLoading ? <> : } - {getString('cde.deleteGitspace')} + + {getString('cde.viewGitspace')} } /> @@ -282,7 +264,26 @@ const ActionMenu = ({ } /> - {config?.ide && ( + { + try { + if (!actionLoading) { + e.preventDefault() + e.stopPropagation() + await handleStartStop() + await refreshList() + } + } catch (error) { + showError(getErrorMessage(error)) + } + }} + text={ + + + + } + /> + {config?.ide && state == GitspaceStatus.RUNNING && ( { e.preventDefault() @@ -300,6 +301,15 @@ const ActionMenu = ({ } /> )} + void} + text={ + + {deleteLoading ? <> : } + {getString('cde.deleteGitspace')} + + } + /> ) @@ -310,7 +320,10 @@ interface RenderActionsProps extends CellProps { } export const RenderActions = ({ row, refreshList }: RenderActionsProps) => { + const { getString } = useStrings() + const { showError, showSuccess } = useToaster() const details = row.original + const { config } = details const { projectIdentifier, orgIdentifier, accountIdentifier } = useGetCDEAPIParams() as CDEPathParams const { mutate: deleteGitspace, loading: deleteLoading } = useDeleteGitspace({ projectIdentifier, @@ -329,6 +342,27 @@ export const RenderActions = ({ row, refreshList }: RenderActionsProps) => { action: details?.state === GitspaceStatus.RUNNING ? GitspaceActionType.STOP : GitspaceActionType.START }) } + + const confirmDelete = useConfirmAct() + + const handleDelete = async (e: React.MouseEvent) => { + confirmDelete({ + title: getString('cde.deleteGitspaceTitle'), + message: getString('cde.deleteGitspaceText', { name: config?.name }), + action: async () => { + try { + e.preventDefault() + e.stopPropagation() + await deleteGitspace(config?.id || '') + showSuccess(getString('cde.deleteSuccess')) + await refreshList() + } catch (exception) { + showError(getErrorMessage(exception)) + } + } + }) + } + return ( { @@ -342,7 +376,7 @@ export const RenderActions = ({ row, refreshList }: RenderActionsProps) => { data={details} actionLoading={actionLoading} deleteLoading={deleteLoading} - deleteGitspace={deleteGitspace} + deleteGitspace={handleDelete} refreshList={refreshList} handleStartStop={handleStartStop} /> @@ -367,7 +401,6 @@ export const ListGitspaces = ({ const history = useHistory() const { getString } = useStrings() const { routes } = useAppContext() - const { showError } = useToaster() return ( @@ -375,31 +408,38 @@ export const ListGitspaces = ({ className={css.table} onRowClick={row => { - if (row?.config?.space_path && row?.config?.id) { + const pathparamsList = row?.config?.space_path?.split('/') || [] + const projectIdentifier = pathparamsList[pathparamsList.length - 1] || '' + + if (row?.state === GitspaceStatus.RUNNING) { + if (row?.config?.ide === IDEType.VSCODE) { + window.open(`vscode://harness-inc.gitspaces/${projectIdentifier}/${row?.config?.id}`, '_blank') + } else { + window.open(row?.url, '_blank') + } + } else { history.push( routes.toCDEGitspaceDetail({ - space: row?.config?.space_path, - gitspaceId: row?.config?.id + space: row?.config?.space_path as string, + gitspaceId: row?.config?.id as string }) ) - } else { - showError(getString('cde.details.wrongIdentifier')) } }} columns={[ { id: 'gitspaces', - Header: 'Gitspaces', + Header: getString('cde.gitspaces'), Cell: RenderGitspaceName }, { id: 'repository', - Header: 'REPOSITORY & BRANCH', + Header: getString('cde.repositoryAndBranch'), Cell: RenderRepository }, { id: 'lastactivity', - Header: 'Last Active', + Header: getString('cde.sessionDuration'), Cell: RenderLastActivity }, { diff --git a/web/src/cde/pages/GitspaceDetail/GitspaceDetail.tsx b/web/src/cde/pages/GitspaceDetail/GitspaceDetail.tsx index 6903a033ed..d1ecd98741 100644 --- a/web/src/cde/pages/GitspaceDetail/GitspaceDetail.tsx +++ b/web/src/cde/pages/GitspaceDetail/GitspaceDetail.tsx @@ -15,8 +15,9 @@ */ import React, { useEffect, useState } from 'react' -import { Breadcrumbs, Layout, Page, useToaster } from '@harnessio/uicore' +import { Breadcrumbs, Layout, Page, useToaster, Text } from '@harnessio/uicore' import { useParams } from 'react-router-dom' +import { Color, FontVariation } from '@harnessio/design-system' import { GitspaceDetails } from 'cde/components/GitspaceDetails/GitspaceDetails' import { useGetSpaceParam } from 'hooks/useGetSpaceParam' import { useAppContext } from 'AppContext' @@ -24,6 +25,7 @@ import { GitspaceLogs } from 'cde/components/GitspaceLogs/GitspaceLogs' import { useStrings } from 'framework/strings' import { CDEPathParams, useGetCDEAPIParams } from 'cde/hooks/useGetCDEAPIParams' import { useQueryParams } from 'hooks/useQueryParams' +import { useUpdateQueryParams } from 'hooks/useUpdateQueryParams' import { useGetGitspace, useGetGitspaceInstanceLogs, useGitspaceAction } from 'services/cde' import { getErrorMessage } from 'utils/Utils' import { GitspaceActionType, GitspaceStatus } from 'cde/constants' @@ -36,6 +38,7 @@ const GitspaceDetail = () => { const space = useGetSpaceParam() const { routes } = useAppContext() const { gitspaceId = '' } = useParams<{ gitspaceId?: string }>() + const { updateQueryParams } = useUpdateQueryParams<{ redirectFrom?: string }>() const { accountIdentifier, orgIdentifier, projectIdentifier } = useGetCDEAPIParams() as CDEPathParams const { redirectFrom = '' } = useQueryParams<{ redirectFrom?: string }>() const [startTriggred, setStartTriggred] = useState(false) @@ -81,21 +84,31 @@ const GitspaceDetail = () => { await mutate({ action: GitspaceActionType.START }) await refetch() await refetchLogs() + updateQueryParams({ redirectFrom: undefined }) } catch (err) { showError(getErrorMessage(err)) } } } - startTrigger() - }, [redirectFrom, mutateLoading, startTriggred]) - - const isfetchingInProgress = (startTriggred && state === GitspaceStatus.STOPPED && !startError) || mutateLoading + if (state && state !== GitspaceStatus.RUNNING && redirectFrom) { + startTrigger() + } + }, [state, redirectFrom, mutateLoading, startTriggred]) return ( <> + + {` ${getString('cde.gitspaceDetail')} ${data?.config?.name}`} + + + {data?.config?.id} + + + } breadcrumbs={ @@ -107,7 +120,7 @@ const GitspaceDetail = () => { }, { url: routes.toCDEGitspaceDetail({ space, gitspaceId }), - label: `${getString('cde.gitpsaceDetail')} ${gitspaceId}` + label: getString('cde.gitspaceDetail') } ]} /> @@ -129,7 +142,6 @@ const GitspaceDetail = () => { mutate={mutate} actionError={startError} mutateLoading={mutateLoading} - isfetchingInProgress={isfetchingInProgress} /> diff --git a/web/src/cde/pages/GitspacesListing/GitspacesListing.tsx b/web/src/cde/pages/GitspacesListing/GitspacesListing.tsx index 66e34c170a..f3757614bf 100644 --- a/web/src/cde/pages/GitspacesListing/GitspacesListing.tsx +++ b/web/src/cde/pages/GitspacesListing/GitspacesListing.tsx @@ -15,16 +15,7 @@ */ import React, { useEffect } from 'react' -import { - Breadcrumbs, - Text, - Button, - ButtonVariation, - ExpandingSearchInput, - Layout, - Page, - Container -} from '@harnessio/uicore' +import { Breadcrumbs, Text, Button, ButtonVariation, Layout, Page, Container } from '@harnessio/uicore' import { FontVariation } from '@harnessio/design-system' import { useHistory } from 'react-router-dom' import { ListGitspaces } from 'cde/components/ListGitspaces/ListGitspaces' @@ -72,29 +63,28 @@ const GitspacesListing = () => { return ( <> } /> + + + - - {getString('cde.manageGitspaces')} - - { image: noSpace, message: getString('cde.noGitspaces') }}> - {Boolean(data) && } diff --git a/web/src/framework/strings/stringTypes.ts b/web/src/framework/strings/stringTypes.ts index 533aac2f4d..c190ab81d9 100644 --- a/web/src/framework/strings/stringTypes.ts +++ b/web/src/framework/strings/stringTypes.ts @@ -140,11 +140,15 @@ export interface StringsMap { cancelImportConfirm: string cancelledImport: string 'cde.branchPlaceholder': string + 'cde.branchValidationMessage': string 'cde.cloudDeveloperExperience': string 'cde.cpu': string 'cde.createGitspace': string 'cde.createRepo': string 'cde.deleteGitspace': string + 'cde.deleteGitspaceText': string + 'cde.deleteGitspaceTitle': string + 'cde.deleteSuccess': string 'cde.details.actions': string 'cde.details.fetchingDetails': string 'cde.details.fetchingGitspace': string @@ -165,22 +169,23 @@ export interface StringsMap { 'cde.disk': string 'cde.editGitspace': string 'cde.eventTimeline': string - 'cde.gitpsaceDetail': string + 'cde.gitspaceDetail': string 'cde.gitspaceUpdateSuccess': string 'cde.gitspaces': string 'cde.hours': string 'cde.ide.browser': string 'cde.ide.desktop': string - 'cde.ide.ide': string 'cde.ide.openBrowser': string 'cde.ide.openVSCode': string 'cde.ide.selectIDE': string + 'cde.ide.title': string 'cde.ide.vsCode': string 'cde.introText1': string 'cde.introText2': string 'cde.introText3': string 'cde.logs': string 'cde.machine': string + 'cde.machineValidationMessage': string 'cde.manageGitspaces': string 'cde.memory': string 'cde.na': string @@ -192,6 +197,8 @@ export interface StringsMap { 'cde.or': string 'cde.region': string 'cde.regionSelectWarning': string + 'cde.regionValidationMessage': string + 'cde.repoValidationMessage': string 'cde.repository.continueWith': string 'cde.repository.pasteRepo': string 'cde.repository.pasterRepoSubtext': string @@ -200,9 +207,14 @@ export interface StringsMap { 'cde.repository.repo': string 'cde.repository.repositoryURL': string 'cde.repository.selectRepository': string + 'cde.repositoryAndBranch': string 'cde.retry': string + 'cde.sessionDuration': string + 'cde.startingGitspace': string + 'cde.stopingGitspace': string 'cde.updateGitspace': string 'cde.used': string + 'cde.viewGitspace': string changePassword: string changePasswordSuccesfully: string changeRepoVis: string diff --git a/web/src/i18n/strings.en.yaml b/web/src/i18n/strings.en.yaml index 353047b3bb..e49580e899 100644 --- a/web/src/i18n/strings.en.yaml +++ b/web/src/i18n/strings.en.yaml @@ -1122,10 +1122,11 @@ cde: introText1: Get ready to code in seconds introText2: Isolated cloud development environments with your favorite editor introText3: Connect in seconds with a repository - cloudDeveloperExperience: Cloud Developer Environments + cloudDeveloperExperience: Cloud Development Environments createGitspace: Create Gitspace deleteGitspace: Delete Gitspace editGitspace: Edit Gitspace + viewGitspace: View Gitspace Detail updateGitspace: Update Gitspace gitspaces: Gitspaces noRepo: Don’t have a Git repo? @@ -1133,7 +1134,7 @@ cde: newGitspace: + New Gitspace manageGitspaces: Manage Gitspaces noGitspaces: You have not created any Gitspace yet - gitpsaceDetail: Gitspace Detail + gitspaceDetail: Gitspace Detail machine: Machine region: Region or: OR @@ -1150,6 +1151,17 @@ cde: disk: Disk regionSelectWarning: Select a region first gitspaceUpdateSuccess: Gitspace updated successfully + regionValidationMessage: Region is required + repoValidationMessage: Repository is required + branchValidationMessage: Branch is required + machineValidationMessage: Machine Type is required + startingGitspace: Starting Gitspace + stopingGitspace: Stopping Gitspace + sessionDuration: Last Started + deleteGitspaceTitle: Delete Gitspace + deleteGitspaceText: "Are you sure to delete the gitspace, '{{name}}'?" + deleteSuccess: Gitspace deleted succesfully + repositoryAndBranch: Repository & Branch details: actions: More Actions openEditor: Open VS Code Editor @@ -1178,9 +1190,9 @@ cde: continueWith: Continue with repositoryURL: Repository URL ide: - browser: Browser - desktop: Desktop - ide: IDE + browser: VS Code Browser + desktop: VS Code Desktop + title: IDE selectIDE: Select IDE vsCode: VS Code openVSCode: Open in VS Code Desktop diff --git a/web/src/services/cde/index.tsx b/web/src/services/cde/index.tsx index 7f171cea53..c8c94742d5 100644 --- a/web/src/services/cde/index.tsx +++ b/web/src/services/cde/index.tsx @@ -32,7 +32,7 @@ export type EnumGitspaceEventType = | 'agentGitspaceStateReportStopped' | 'agentGitspaceStateReportUnknown' -export type EnumGitspaceStateType = 'running' | 'stopped' | 'unknown' | 'error' +export type EnumGitspaceStateType = 'running' | 'stopped' | 'error' export type EnumIDEType = 'vsCode' | 'vsCodeWeb' @@ -271,6 +271,58 @@ export const useListGitspacesForAccount = ({ accountIdentifier, ...props }: UseL { base: getConfig('cde/api/v1'), pathParams: { accountIdentifier }, ...props } ) +export interface ListInfraProviderResourcesForAccountPathParams { + /** + * account identifier. + */ + accountIdentifier: string + /** + * infra Provider Config Identifier. + */ + infraProviderConfigIdentifier: string +} + +export type ListInfraProviderResourcesForAccountProps = Omit< + GetProps, + 'path' +> & + ListInfraProviderResourcesForAccountPathParams + +/** + * List infraProvider Resources + */ +export const ListInfraProviderResourcesForAccount = ({ + accountIdentifier, + infraProviderConfigIdentifier, + ...props +}: ListInfraProviderResourcesForAccountProps) => ( + + path={`/accounts/${accountIdentifier}/infraproviders/${infraProviderConfigIdentifier}/resources`} + base={getConfig('cde/api/v1')} + {...props} + /> +) + +export type UseListInfraProviderResourcesForAccountProps = Omit< + UseGetProps, + 'path' +> & + ListInfraProviderResourcesForAccountPathParams + +/** + * List infraProvider Resources + */ +export const useListInfraProviderResourcesForAccount = ({ + accountIdentifier, + infraProviderConfigIdentifier, + ...props +}: UseListInfraProviderResourcesForAccountProps) => + useGet( + (paramsInPath: ListInfraProviderResourcesForAccountPathParams) => + `/accounts/${paramsInPath.accountIdentifier}/infraproviders/${paramsInPath.infraProviderConfigIdentifier}/resources`, + { base: getConfig('cde/api/v1'), pathParams: { accountIdentifier, infraProviderConfigIdentifier }, ...props } + ) + export interface GetCodeRepositoryPathParams { /** * account identifier. diff --git a/web/src/services/cde/swagger.yaml b/web/src/services/cde/swagger.yaml index 9984d583df..656e090ad9 100644 --- a/web/src/services/cde/swagger.yaml +++ b/web/src/services/cde/swagger.yaml @@ -30,6 +30,32 @@ paths: summary: List gitspaces for account tags: - gitspaces + /accounts/{accountIdentifier}/infraproviders/{infraProviderConfigIdentifier}/resources: + get: + operationId: listInfraProviderResourcesForAccount + parameters: + - description: account identifier. + in: path + name: accountIdentifier + required: true + schema: + type: string + - description: infra Provider Config Identifier. + in: path + name: infraProviderConfigIdentifier + required: true + schema: + type: string + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/OpenapiListInfraProviderResourceResponse' + description: OK + summary: List infraProvider Resources + tags: + - infraproviderresources /accounts/{accountIdentifier}/orgs/{orgIdentifier}/projects/{projectIdentifier}/coderepository: post: operationId: getCodeRepository @@ -655,7 +681,6 @@ components: enum: - running - stopped - - unknown - error type: string EnumIDEType: