feat: [CDE-243]: fixes cde logs in gitspace details (#2529)

* feat: [CDE-243]: fixes cde logs in gitspace details
* feat: [CDE-243]: fixes cde logs in gitspace details
pull/3545/head
Deepesh Kumar 2024-08-19 10:55:30 +00:00 committed by Harness
parent 54113d4b06
commit 471dead872
9 changed files with 111 additions and 29 deletions

View File

@ -1,4 +1,4 @@
import React from 'react' import React, { useEffect, useRef, useState } from 'react'
import { PopoverInteractionKind, PopoverPosition } from '@blueprintjs/core' import { PopoverInteractionKind, PopoverPosition } from '@blueprintjs/core'
import { Container, Layout, Button, ButtonVariation } from '@harnessio/uicore' import { Container, Layout, Button, ButtonVariation } from '@harnessio/uicore'
import css from './CDECustomDropdown.module.scss' import css from './CDECustomDropdown.module.scss'
@ -7,13 +7,26 @@ interface CDECustomDropdownProps {
leftElement: React.ReactNode leftElement: React.ReactNode
label: React.ReactNode label: React.ReactNode
menu: React.ReactNode menu: React.ReactNode
overridePopOverWidth?: boolean
} }
export const CDECustomDropdown = ({ label, menu, leftElement }: CDECustomDropdownProps) => { export const CDECustomDropdown = ({ label, menu, leftElement, overridePopOverWidth }: CDECustomDropdownProps) => {
const buttonRef = useRef<HTMLDivElement | null>(null)
const [popoverWidth, setPopoverWidth] = useState(0)
useEffect(() => {
if (
buttonRef?.current?.getBoundingClientRect()?.width &&
buttonRef?.current?.getBoundingClientRect()?.width !== popoverWidth
) {
setPopoverWidth(buttonRef?.current?.getBoundingClientRect()?.width)
}
}, [buttonRef?.current, popoverWidth])
return ( return (
<Layout.Horizontal className={css.main}> <Layout.Horizontal className={css.main}>
<Container width="70%">{leftElement}</Container> <Container width="70%">{leftElement}</Container>
<Container width="30%"> <Container width="30%" ref={buttonRef}>
<Button <Button
height="45px" height="45px"
width="100%" width="100%"
@ -29,7 +42,7 @@ export const CDECustomDropdown = ({ label, menu, leftElement }: CDECustomDropdow
popoverClassName: css.popover popoverClassName: css.popover
}} }}
tooltip={ tooltip={
<Container className={css.listContainer} width="100%"> <Container className={css.listContainer} width={overridePopOverWidth ? '100%' : popoverWidth}>
{menu} {menu}
</Container> </Container>
} }

View File

@ -64,7 +64,7 @@ export const CDEIDESelect = ({
<MenuItem <MenuItem
key={item.value} key={item.value}
text={ text={
<Layout.Horizontal> <Layout.Horizontal width="90%" flex={{ alignItems: 'center', justifyContent: 'space-between' }}>
<img height={16} width={16} src={item.img} /> <img height={16} width={16} src={item.img} />
<Text>{item.label}</Text> <Text>{item.label}</Text>
</Layout.Horizontal> </Layout.Horizontal>

View File

@ -2,10 +2,13 @@ import { Container } from '@harnessio/uicore'
import cx from 'classnames' import cx from 'classnames'
import React, { useEffect, useRef } from 'react' import React, { useEffect, useRef } from 'react'
import { lineElement } from 'components/LogViewer/LogViewer' import { lineElement } from 'components/LogViewer/LogViewer'
import { useAppContext } from 'AppContext'
import type { LogData } from '../../hooks/useGetLogStream' import type { LogData } from '../../hooks/useGetLogStream'
import { parseLogString } from './ContainerLogs.utils'
import css from './ContainerLogs.module.scss' import css from './ContainerLogs.module.scss'
const ContainerLogs = ({ data }: { data: LogData[] }) => { const ContainerLogs = ({ data }: { data: LogData[] }) => {
const { standalone } = useAppContext()
const localRef = useRef<HTMLDivElement | null>() const localRef = useRef<HTMLDivElement | null>()
useEffect(() => { useEffect(() => {
@ -18,12 +21,15 @@ const ContainerLogs = ({ data }: { data: LogData[] }) => {
localRef.current.innerHTML = '' localRef.current.innerHTML = ''
} }
if (data) { if (data) {
data?.forEach((line: any) => { const logData = standalone ? data : parseLogString(data as unknown as string)
logData?.forEach((line: any) => {
const linePos = line.pos + 1 const linePos = line.pos + 1
const localDate = new Date(line.time) const localDate = new Date(line.time)
// Format date to a more readable format (local time) // Format date to a more readable format (local time)
const formattedDate = localDate.toLocaleString() const formattedDate = localDate.toLocaleString()
fragment.appendChild(lineElement(`${linePos} ${formattedDate.replace(',', '')} ${line.out}`)) fragment.appendChild(
lineElement(`${linePos} ${formattedDate.replace(',', '')} ${standalone ? line.out : line.message}`)
)
}) })
logContainer.appendChild(fragment) logContainer.appendChild(fragment)

View File

@ -0,0 +1,38 @@
export function parseLogString(logString: string) {
if (!logString) {
return ''
}
const logEntries = logString.trim().split('\n')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const parsedLogs: any = []
logEntries.forEach((entry, lineIndex) => {
// Parse the entry as JSON
const jsonEntry = JSON.parse(entry)
// Apply the regex to the 'out' field
const parts = (jsonEntry?.message).match(/time="([^"]+)" level=([^ ]+) msg="([^"]+)"(.*)/)
if (parts) {
const [, time, level, message, details, out] = parts
const detailParts = details.trim().split(' ')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const detailDict: any = {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
detailParts.forEach((part: any) => {
if (part.includes('=')) {
const [key, value] = part.split('=')
detailDict[key.trim()] = value.trim()
}
})
parsedLogs.push({ time, level, message, out, details: detailDict, pos: jsonEntry.pos, logLevel: jsonEntry.level })
} else {
parsedLogs.push({
time: jsonEntry.time,
level: jsonEntry.level,
message: jsonEntry?.message,
pos: lineIndex,
logLevel: jsonEntry.level
})
}
})
return parsedLogs
}

View File

@ -20,7 +20,7 @@ import { Layout } from '@harnessio/uicore'
import { useFormikContext } from 'formik' import { useFormikContext } from 'formik'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { CDEPathParams, useGetCDEAPIParams } from 'cde-gitness/hooks/useGetCDEAPIParams' import { CDEPathParams, useGetCDEAPIParams } from 'cde-gitness/hooks/useGetCDEAPIParams'
import { OpenapiCreateGitspaceRequest, useListInfraProviderResources } from 'services/cde' import { OpenapiCreateGitspaceRequest, useGetInfraProvider } from 'services/cde'
import { SelectRegion } from '../SelectRegion/SelectRegion' import { SelectRegion } from '../SelectRegion/SelectRegion'
import { SelectMachine } from '../SelectMachine/SelectMachine' import { SelectMachine } from '../SelectMachine/SelectMachine'
@ -32,7 +32,7 @@ export const SelectInfraProvider = () => {
// infraProviderConfigIdentifier: 'HARNESS_GCP' // infraProviderConfigIdentifier: 'HARNESS_GCP'
// }) // })
const { data } = useListInfraProviderResources({ const { data } = useGetInfraProvider({
accountIdentifier, accountIdentifier,
projectIdentifier, projectIdentifier,
orgIdentifier, orgIdentifier,
@ -41,11 +41,11 @@ export const SelectInfraProvider = () => {
const { gitspaceId = '' } = useParams<{ gitspaceId?: string }>() const { gitspaceId = '' } = useParams<{ gitspaceId?: string }>()
const optionsList = data && isObject(data) ? data : [] const optionsList = data?.resources && isObject(data?.resources) ? data?.resources : []
useEffect(() => { useEffect(() => {
if (gitspaceId && values.resource_identifier && optionsList.length) { if (gitspaceId && values.resource_identifier && optionsList.length) {
const match = optionsList.find(item => item.id === values.resource_identifier) const match = optionsList.find(item => item.identifier === values.resource_identifier)
if (values?.metadata?.region !== match?.region) { if (values?.metadata?.region !== match?.region) {
onChange('metadata.region', match?.region?.toLowerCase()) onChange('metadata.region', match?.region?.toLowerCase())
} }
@ -53,7 +53,7 @@ export const SelectInfraProvider = () => {
}, [gitspaceId, values.resource_identifier, values?.metadata?.region, optionsList.map(i => i.name).join('')]) }, [gitspaceId, values.resource_identifier, values?.metadata?.region, optionsList.map(i => i.name).join('')])
const regionOptions = Object.entries(groupBy(optionsList, 'region')).map(i => { const regionOptions = Object.entries(groupBy(optionsList, 'region')).map(i => {
return { label: i[0], value: i[1] } return { label: i[0] as string, value: i[1] }
}) })
const machineOptions = const machineOptions =

View File

@ -21,7 +21,7 @@ import { Cpu } from 'iconoir-react'
import { useFormikContext } from 'formik' import { useFormikContext } from 'formik'
import { FontVariation } from '@harnessio/design-system' import { FontVariation } from '@harnessio/design-system'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import type { OpenapiCreateGitspaceRequest, TypesInfraProviderResourceResponse } from 'services/cde' import type { OpenapiCreateGitspaceRequest, TypesInfraProviderResource } from 'services/cde'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import { CDECustomDropdown } from 'cde-gitness/components/CDECustomDropdown/CDECustomDropdown' import { CDECustomDropdown } from 'cde-gitness/components/CDECustomDropdown/CDECustomDropdown'
import css from './SelectMachine.module.scss' import css from './SelectMachine.module.scss'
@ -37,8 +37,8 @@ export const labelToMachineId = {
} }
interface SelectMachineInterface { interface SelectMachineInterface {
options: TypesInfraProviderResourceResponse[] options: TypesInfraProviderResource[]
defaultValue: TypesInfraProviderResourceResponse defaultValue: TypesInfraProviderResource
} }
export const SelectMachine = ({ options, defaultValue }: SelectMachineInterface) => { export const SelectMachine = ({ options, defaultValue }: SelectMachineInterface) => {
@ -48,9 +48,9 @@ export const SelectMachine = ({ options, defaultValue }: SelectMachineInterface)
const { gitspaceId = '' } = useParams<{ gitspaceId?: string }>() const { gitspaceId = '' } = useParams<{ gitspaceId?: string }>()
const machineTypes = options.map(item => { const machineTypes = options.map(item => {
const { cpu, disk, memory, id, name } = item const { cpu, disk, memory, identifier, name } = item
return { return {
id, identifier,
label: name, label: name,
cpu, cpu,
disk, disk,
@ -60,15 +60,16 @@ export const SelectMachine = ({ options, defaultValue }: SelectMachineInterface)
useEffect(() => { useEffect(() => {
if (defaultValue && !gitspaceId) { if (defaultValue && !gitspaceId) {
onChange('resource_identifier', defaultValue.id) onChange('resource_identifier', defaultValue.identifier)
} }
}, [defaultValue?.id, gitspaceId]) }, [defaultValue?.identifier, gitspaceId])
const data = (machineTypes?.find(item => item.id === machine) || {}) as (typeof machineTypes)[0] const data = (machineTypes?.find(item => item.identifier === machine) || {}) as (typeof machineTypes)[0]
return ( return (
<Container> <Container>
<CDECustomDropdown <CDECustomDropdown
overridePopOverWidth
leftElement={ leftElement={
<Layout.Horizontal> <Layout.Horizontal>
<Cpu height={20} width={20} style={{ marginRight: '8px', alignItems: 'center' }} /> <Cpu height={20} width={20} style={{ marginRight: '8px', alignItems: 'center' }} />
@ -93,8 +94,8 @@ export const SelectMachine = ({ options, defaultValue }: SelectMachineInterface)
{machineTypes.map(item => { {machineTypes.map(item => {
return ( return (
<MenuItem <MenuItem
key={item.id} key={item.identifier}
active={values.resource_identifier === item.id} active={values.resource_identifier === item.identifier}
text={ text={
<Layout.Vertical> <Layout.Vertical>
<Text font={{ size: 'normal', weight: 'bold' }}>{item.label?.toUpperCase()}</Text> <Text font={{ size: 'normal', weight: 'bold' }}>{item.label?.toUpperCase()}</Text>
@ -112,7 +113,7 @@ export const SelectMachine = ({ options, defaultValue }: SelectMachineInterface)
</Layout.Vertical> </Layout.Vertical>
} }
onClick={() => { onClick={() => {
onChange('resource_identifier', item.id || '') onChange('resource_identifier', item.identifier || '')
}} }}
/> />
) )

View File

@ -21,7 +21,7 @@ import { Map } from 'iconoir-react'
import { useFormikContext } from 'formik' import { useFormikContext } from 'formik'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import { GitspaceRegion } from 'cde-gitness/constants' import { GitspaceRegion } from 'cde-gitness/constants'
import type { OpenapiCreateGitspaceRequest, TypesInfraProviderResourceResponse } from 'services/cde' import type { OpenapiCreateGitspaceRequest, TypesInfraProviderResource } from 'services/cde'
import { CDECustomDropdown } from 'cde-gitness/components/CDECustomDropdown/CDECustomDropdown' import { CDECustomDropdown } from 'cde-gitness/components/CDECustomDropdown/CDECustomDropdown'
import USWest from './assests/USWest.png' import USWest from './assests/USWest.png'
import USEast from './assests/USEast.png' import USEast from './assests/USEast.png'
@ -31,8 +31,8 @@ import Empty from './assests/Empty.png'
interface SelectRegionInterface { interface SelectRegionInterface {
disabled?: boolean disabled?: boolean
defaultValue: { label: string; value: TypesInfraProviderResourceResponse[] } defaultValue: { label: string; value: TypesInfraProviderResource[] }
options: { label: string; value: TypesInfraProviderResourceResponse[] }[] options: { label: string; value: TypesInfraProviderResource[] }[]
} }
export const getMapFromRegion = (region: string) => { export const getMapFromRegion = (region: string) => {
@ -68,6 +68,7 @@ export const SelectRegion = ({ options, disabled, defaultValue }: SelectRegionIn
return ( return (
<Container> <Container>
<CDECustomDropdown <CDECustomDropdown
overridePopOverWidth
label={ label={
<Layout.Horizontal spacing={'small'} flex={{ alignItems: 'center', justifyContent: 'flex-start' }}> <Layout.Horizontal spacing={'small'} flex={{ alignItems: 'center', justifyContent: 'flex-start' }}>
<Layout.Vertical> <Layout.Vertical>

View File

@ -1,4 +1,5 @@
import { useGet } from 'restful-react' import { useGet } from 'restful-react'
import { useEffect } from 'react'
import { useGetCDEAPIParams } from 'cde-gitness/hooks/useGetCDEAPIParams' import { useGetCDEAPIParams } from 'cde-gitness/hooks/useGetCDEAPIParams'
import { useAppContext } from 'AppContext' import { useAppContext } from 'AppContext'
import { useGetGitspaceInstanceLogs } from 'services/cde' import { useGetGitspaceInstanceLogs } from 'services/cde'
@ -22,5 +23,11 @@ export const useGitspacesLogs = ({ gitspaceId }: { gitspaceId: string }) => {
lazy: true lazy: true
}) })
useEffect(() => {
if (!standalone) {
cde.refetch()
}
}, [])
return standalone ? gitness : cde return standalone ? gitness : cde
} }

View File

@ -62,7 +62,12 @@ const GitspaceDetails = () => {
const { data: eventData, refetch: refetchEventData } = useGitspaceEvents({ gitspaceId }) const { data: eventData, refetch: refetchEventData } = useGitspaceEvents({ gitspaceId })
const { refetch: refetchLogsData, response, error: streamLogsError } = useGitspacesLogs({ gitspaceId }) const {
data: responseData,
refetch: refetchLogsData,
response,
error: streamLogsError
} = useGitspacesLogs({ gitspaceId })
const { mutate: actionMutate, loading: mutateLoading } = useGitspaceActions({ gitspaceId }) const { mutate: actionMutate, loading: mutateLoading } = useGitspaceActions({ gitspaceId })
@ -112,6 +117,17 @@ const GitspaceDetails = () => {
} }
) )
usePolling(
async () => {
await refetchLogsData()
},
{
pollingInterval: 10000,
startCondition: (eventData?.[eventData?.length - 1]?.event as string) === 'agent_gitspace_creation_start',
stopCondition: pollingCondition
}
)
useEffect(() => { useEffect(() => {
const startTrigger = async () => { const startTrigger = async () => {
if (redirectFrom && !startTriggred && !mutateLoading) { if (redirectFrom && !startTriggred && !mutateLoading) {
@ -135,7 +151,7 @@ const GitspaceDetails = () => {
} }
}, [data?.state, redirectFrom, mutateLoading, startTriggred]) }, [data?.state, redirectFrom, mutateLoading, startTriggred])
const formattedlogsdata = useGetLogStream({ response }) const formattedlogsdata = useGetLogStream(standalone ? { response } : { response: undefined })
const confirmDelete = useConfirmAct() const confirmDelete = useConfirmAct()
@ -392,7 +408,7 @@ const GitspaceDetails = () => {
id="logsCard" id="logsCard"
details={ details={
<Container> <Container>
<ContainerLogs data={formattedlogsdata.data} /> <ContainerLogs data={standalone ? formattedlogsdata.data : responseData} />
</Container> </Container>
} }
/> />