-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c3710b8
commit 65f028b
Showing
9 changed files
with
321 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
export interface SourceFormData {} | ||
export interface SourceFormData { | ||
otelServiceName: string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { DISPLAY_TITLES, type Source } from '@odigos/ui-utils' | ||
import type { DataCardFieldsProps } from '@odigos/ui-components' | ||
|
||
const buildCard = (source: Source) => { | ||
const { name, kind, namespace } = source | ||
|
||
const arr: DataCardFieldsProps['data'] = [ | ||
{ title: DISPLAY_TITLES.NAMESPACE, value: namespace }, | ||
{ title: DISPLAY_TITLES.KIND, value: kind }, | ||
{ title: DISPLAY_TITLES.NAME, value: name, tooltip: 'K8s resource name' }, | ||
] | ||
|
||
return arr | ||
} | ||
|
||
export { buildCard } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
import React, { type FC, useMemo, useState } from 'react' | ||
import styled from 'styled-components' | ||
import { buildCard } from './build-card' | ||
import { SourceForm } from '../source-form' | ||
import { useDrawerStore } from '../../store' | ||
import type { SourceFormData } from '../../@types' | ||
import { CodeIcon, ListIcon } from '@odigos/ui-icons' | ||
import { OverviewDrawer, useSourceFormData } from '../../helpers' | ||
import { ConditionDetails, DATA_CARD_FIELD_TYPES, DataCard, type DataCardFieldsProps, Segment } from '@odigos/ui-components' | ||
import { type DescribeSource, DISPLAY_TITLES, ENTITY_TYPES, getEntityIcon, safeJsonStringify, type Source, type WorkloadId } from '@odigos/ui-utils' | ||
|
||
interface SourceDrawerProps { | ||
sources: Source[] | ||
persistSources: ( | ||
selectAppsList: { [namespace: string]: Pick<Source, 'name' | 'kind' | 'selected'>[] }, | ||
futureSelectAppsList: { [namespace: string]: boolean } | ||
) => Promise<void> | ||
updateSource: (sourceId: WorkloadId, payload: SourceFormData) => Promise<void> | ||
describe: DescribeSource | ||
} | ||
|
||
const FormContainer = styled.div` | ||
width: 100%; | ||
height: 100%; | ||
max-height: calc(100vh - 220px); | ||
overflow: overlay; | ||
overflow-y: auto; | ||
` | ||
|
||
const DataContainer = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: 12px; | ||
` | ||
|
||
const SourceDrawer: FC<SourceDrawerProps> = ({ sources, persistSources, updateSource, describe }) => { | ||
const { drawerType, drawerEntityId, setDrawerEntityId, setDrawerType } = useDrawerStore() | ||
|
||
const isOpen = drawerType !== ENTITY_TYPES.SOURCE | ||
const onClose = () => { | ||
setDrawerType(null) | ||
setDrawerEntityId(null) | ||
} | ||
|
||
const [isEditing, setIsEditing] = useState(false) | ||
const [isFormDirty, setIsFormDirty] = useState(false) | ||
const [isPrettyMode, setIsPrettyMode] = useState(true) // for "describe source" | ||
|
||
const { formData, handleFormChange, resetFormData, loadFormWithDrawerItem } = useSourceFormData() | ||
// const { data: describe, restructureForPrettyMode } = useDescribeSource(drawerEntityId as WorkloadId) | ||
|
||
const thisItem = useMemo(() => { | ||
if (isOpen) return null | ||
|
||
const found = sources?.find( | ||
(x) => | ||
x.namespace === (drawerEntityId as WorkloadId).namespace && | ||
x.name === (drawerEntityId as WorkloadId).name && | ||
x.kind === (drawerEntityId as WorkloadId).kind | ||
) | ||
if (!!found) loadFormWithDrawerItem(found) | ||
|
||
return found | ||
}, [isOpen, drawerEntityId, sources]) | ||
|
||
if (!thisItem) return null | ||
|
||
const containersData = | ||
thisItem.containers?.map( | ||
(container) => | ||
({ | ||
type: DATA_CARD_FIELD_TYPES.SOURCE_CONTAINER, | ||
width: '100%', | ||
value: JSON.stringify(container), | ||
} as DataCardFieldsProps['data'][0]) | ||
) || [] | ||
|
||
const handleEdit = (bool?: boolean) => { | ||
setIsEditing(typeof bool === 'boolean' ? bool : true) | ||
} | ||
|
||
const handleCancel = () => { | ||
setIsEditing(false) | ||
setIsFormDirty(false) | ||
handleFormChange('otelServiceName', thisItem.otelServiceName || thisItem.name || '') | ||
} | ||
|
||
const handleDelete = async () => { | ||
const { namespace } = thisItem | ||
await persistSources({ [namespace]: [{ ...thisItem, selected: false }] }, {}) | ||
setIsEditing(false) | ||
setIsFormDirty(false) | ||
resetFormData() | ||
// close drawer, all other cases are handled in OverviewDrawer | ||
onClose() | ||
} | ||
|
||
const handleSave = async () => { | ||
const title = formData.otelServiceName !== thisItem.name ? formData.otelServiceName : '' | ||
handleFormChange('otelServiceName', title) | ||
await updateSource(drawerEntityId as WorkloadId, { ...formData, otelServiceName: title }) | ||
setIsEditing(false) | ||
setIsFormDirty(false) | ||
} | ||
|
||
// This function is used to restructure the data, so that it reflects the output given by "odigos describe" command in the CLI. | ||
// This is not really needed, but it's a nice-to-have feature to make the data more readable. | ||
const restructureForPrettyMode = () => { | ||
if (!describe) return {} | ||
|
||
const payload: Record<string, any> = {} | ||
|
||
const mapObjects = (obj: any, category?: string, options?: { keyPrefix?: string }) => { | ||
if (typeof obj === 'object' && !!obj?.name) { | ||
let key = options?.keyPrefix ? `${options?.keyPrefix}${obj.name}` : obj.name | ||
let val = obj.value | ||
|
||
if (obj.explain) key += `@tooltip=${obj.explain}` | ||
if (obj.status) val += `@status=${obj.status}` | ||
else val += '@status=none' | ||
|
||
if (!!category && !payload[category]) payload[category] = {} | ||
if (!!category) payload[category][key] = val | ||
else payload[key] = val | ||
} | ||
} | ||
|
||
Object.values(describe).forEach((val) => mapObjects(val)) | ||
Object.values(describe?.sourceObjects || {}).forEach((val) => mapObjects(val, 'Sources')) | ||
Object.values(describe?.otelAgents || {}).forEach((val) => mapObjects(val, 'Instrumentation Config')) | ||
describe.otelAgents?.containers?.forEach((obj, i) => | ||
Object.values(obj).forEach((val) => mapObjects(val, 'Instrumentation Config', { keyPrefix: `Container #${i + 1} - ` })) | ||
) | ||
describe.runtimeInfo?.containers?.forEach((obj, i) => | ||
Object.values(obj).forEach((val) => mapObjects(val, 'Runtime Info', { keyPrefix: `Container #${i + 1} - ` })) | ||
) | ||
|
||
payload['Pods'] = { 'Total Pods': `${describe.totalPods}@status=none` } | ||
describe.pods.forEach((obj) => { | ||
Object.values(obj).forEach((val) => mapObjects(val, 'Pods')) | ||
obj.containers?.forEach((containers, i) => { | ||
Object.values(containers).forEach((val) => mapObjects(val, 'Pods', { keyPrefix: `Container #${i + 1} - ` })) | ||
containers?.instrumentationInstances.forEach((obj, i) => | ||
Object.values(obj).forEach((val) => mapObjects(val, 'Pods', { keyPrefix: `Instrumentation Instance #${i + 1} - ` })) | ||
) | ||
}) | ||
}) | ||
|
||
return payload | ||
} | ||
|
||
return ( | ||
<OverviewDrawer | ||
title={thisItem.otelServiceName || thisItem.name} | ||
titleTooltip='This attribute is used to identify the name of the service (service.name) that is generating telemetry data.' | ||
icon={getEntityIcon(ENTITY_TYPES.SOURCE)} | ||
isEdit={isEditing} | ||
isFormDirty={isFormDirty} | ||
onEdit={handleEdit} | ||
onSave={handleSave} | ||
onDelete={handleDelete} | ||
onCancel={handleCancel} | ||
isLastItem={sources.length === 1} | ||
> | ||
{isEditing ? ( | ||
<FormContainer> | ||
<SourceForm | ||
formData={formData} | ||
handleFormChange={(...params) => { | ||
setIsFormDirty(true) | ||
handleFormChange(...params) | ||
}} | ||
/> | ||
</FormContainer> | ||
) : ( | ||
<DataContainer> | ||
<ConditionDetails conditions={thisItem.conditions || []} /> | ||
<DataCard title={DISPLAY_TITLES.SOURCE_DETAILS} data={!!thisItem ? buildCard(thisItem) : []} /> | ||
<DataCard | ||
title={DISPLAY_TITLES.DETECTED_CONTAINERS} | ||
titleBadge={containersData.length} | ||
description={DISPLAY_TITLES.DETECTED_CONTAINERS_DESCRIPTION} | ||
data={containersData} | ||
/> | ||
<DataCard | ||
title={DISPLAY_TITLES.DESCRIBE_SOURCE} | ||
action={ | ||
<Segment | ||
options={[ | ||
{ icon: ListIcon, value: true }, | ||
{ icon: CodeIcon, value: false }, | ||
]} | ||
selected={isPrettyMode} | ||
setSelected={setIsPrettyMode} | ||
/> | ||
} | ||
data={[ | ||
{ | ||
type: DATA_CARD_FIELD_TYPES.CODE, | ||
value: JSON.stringify({ | ||
language: 'json', | ||
code: safeJsonStringify(isPrettyMode ? restructureForPrettyMode() : describe), | ||
pretty: isPrettyMode, | ||
}), | ||
width: 'inherit', | ||
}, | ||
]} | ||
/> | ||
</DataContainer> | ||
)} | ||
</OverviewDrawer> | ||
) | ||
} | ||
|
||
export { SourceDrawer, type SourceDrawerProps } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import React, { useEffect } from 'react' | ||
import { useDrawerStore } from '../../store' | ||
import type { StoryFn } from '@storybook/react' | ||
import { SourceDrawer, type SourceDrawerProps } from '.' | ||
import { ENTITY_TYPES, MOCK_DESCRIBE_SOURCE, MOCK_SOURCES } from '@odigos/ui-utils' | ||
|
||
export default { | ||
title: 'Containers/SourceDrawer', | ||
component: SourceDrawer, | ||
} | ||
|
||
export const Default: StoryFn<SourceDrawerProps> = (props) => { | ||
const { setDrawerType, setDrawerEntityId } = useDrawerStore() | ||
|
||
useEffect(() => { | ||
setDrawerType(ENTITY_TYPES.SOURCE) | ||
setDrawerEntityId({ namespace: MOCK_SOURCES[0].namespace, name: MOCK_SOURCES[0].name, kind: MOCK_SOURCES[0].kind }) | ||
}, []) | ||
|
||
return <SourceDrawer {...props} /> | ||
} | ||
|
||
Default.args = { | ||
sources: MOCK_SOURCES, | ||
persistSources: async () => {}, | ||
updateSource: async () => {}, | ||
describe: MOCK_DESCRIBE_SOURCE, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,17 @@ | ||
import React, { useState } from 'react' | ||
import React from 'react' | ||
import type { StoryFn } from '@storybook/react' | ||
import { SourceForm, type SourceFormProps } from '.' | ||
import { useSourceFormData } from '../../helpers' | ||
|
||
export default { | ||
title: 'Containers/SourceForm', | ||
component: SourceForm, | ||
} | ||
|
||
export const Default: StoryFn<SourceFormProps> = (props) => { | ||
const [formData, setFormData] = useState({ otelServiceName: '' }) | ||
const { formData, handleFormChange } = useSourceFormData() | ||
|
||
return <SourceForm {...props} formData={formData} handleFormChange={(k, v) => setFormData((prev) => ({ ...prev, [k]: v }))} /> | ||
return <SourceForm {...props} formData={formData} handleFormChange={handleFormChange} /> | ||
} | ||
|
||
Default.args = {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { useNotificationStore } from '../../../store' | ||
import type { SourceFormData } from '../../../@types' | ||
import { FORM_ALERTS, NOTIFICATION_TYPE, type Source, useGenericForm } from '@odigos/ui-utils' | ||
|
||
const INITIAL: SourceFormData = { | ||
otelServiceName: '', | ||
} | ||
|
||
export const useSourceFormData = () => { | ||
const { addNotification } = useNotificationStore() | ||
const { formData, formErrors, handleFormChange, handleErrorChange, resetFormData } = useGenericForm<SourceFormData>(INITIAL) | ||
|
||
const validateForm = (params?: { withAlert?: boolean; alertTitle?: string }) => { | ||
const errors: typeof formErrors = {} | ||
let ok = true | ||
|
||
// Sources don't have any specific validations yet, no required fields at this time | ||
|
||
if (!ok && params?.withAlert) { | ||
addNotification({ | ||
type: NOTIFICATION_TYPE.WARNING, | ||
title: params.alertTitle, | ||
message: FORM_ALERTS.REQUIRED_FIELDS, | ||
hideFromHistory: true, | ||
}) | ||
} | ||
|
||
handleErrorChange(undefined, undefined, errors) | ||
|
||
return ok | ||
} | ||
|
||
const loadFormWithDrawerItem = ({ otelServiceName, name }: Source) => { | ||
const updatedData: SourceFormData = { | ||
...INITIAL, | ||
otelServiceName: otelServiceName || name || '', | ||
} | ||
|
||
handleFormChange(undefined, undefined, updatedData) | ||
} | ||
|
||
return { | ||
formData, | ||
formErrors, | ||
handleFormChange, | ||
resetFormData, | ||
validateForm, | ||
loadFormWithDrawerItem, | ||
} | ||
} |