@odigos/ui-kit 0.0.72 → 0.0.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.73](https://github.com/odigos-io/ui-kit/compare/ui-kit-v0.0.72...ui-kit-v0.0.73) (2025-08-06)
4
+
5
+
6
+ ### Features
7
+
8
+ * source-drawer libraries ([#289](https://github.com/odigos-io/ui-kit/issues/289)) ([fd46802](https://github.com/odigos-io/ui-kit/commit/fd468023aeb5a44a322eb28720020946943c47e9))
9
+
3
10
  ## [0.0.72](https://github.com/odigos-io/ui-kit/compare/ui-kit-v0.0.71...ui-kit-v0.0.72) (2025-08-05)
4
11
 
5
12
 
@@ -2,7 +2,7 @@ import { type FC } from 'react';
2
2
  import { type DescribeSource, type Source, type WorkloadId } from '@/types';
3
3
  interface DescribeProps {
4
4
  source: Source;
5
- fetchDescribeSource: (req: {
5
+ fetchSourceDescribe: (req: {
6
6
  variables: WorkloadId;
7
7
  }) => Promise<{
8
8
  data?: {
@@ -1,16 +1,14 @@
1
1
  import { type FC } from 'react';
2
- import { type DescribeSource, type PersistSources, type SourceFormData, type WorkloadId } from '@/types';
2
+ import { type DescribeProps } from './describe';
3
+ import { type LibrariesProps } from './libraries';
4
+ import { type PersistSources, type SourceFormData, type WorkloadId, type Source } from '@/types';
3
5
  interface SourceDrawerProps {
4
6
  persistSources: PersistSources;
5
7
  updateSource: (sourceId: WorkloadId, payload: SourceFormData) => Promise<void>;
6
- fetchDescribeSource: (req: {
7
- variables: WorkloadId;
8
- }) => Promise<{
9
- data?: {
10
- describeSource: DescribeSource;
11
- };
12
- }>;
13
8
  restartWorkloads: (sourceIds: WorkloadId[]) => Promise<void>;
9
+ fetchSourceById: (id: WorkloadId, bypassPaginationLoader?: boolean) => Promise<Source | undefined>;
10
+ fetchSourceDescribe: DescribeProps['fetchSourceDescribe'];
11
+ fetchSourceLibraries: LibrariesProps['fetchSourceLibraries'];
14
12
  }
15
13
  declare const SourceDrawer: FC<SourceDrawerProps>;
16
14
  export { SourceDrawer, type SourceDrawerProps };
@@ -0,0 +1,14 @@
1
+ import { type FC } from 'react';
2
+ import { type InstrumentationInstanceComponent, type Source, type WorkloadId } from '@/types';
3
+ interface LibrariesProps {
4
+ source: Source;
5
+ fetchSourceLibraries: (req: {
6
+ variables: WorkloadId;
7
+ }) => Promise<{
8
+ data?: {
9
+ instrumentationInstanceComponents: InstrumentationInstanceComponent[];
10
+ };
11
+ }>;
12
+ }
13
+ declare const Libraries: FC<LibrariesProps>;
14
+ export { Libraries, type LibrariesProps };
package/lib/containers.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle, useMemo, Fragment, useCallback, Children } from 'react';
2
2
  import styled, { css } from 'styled-components';
3
3
  import { b as DISPLAY_TITLES, T as Theme, U as usePendingStore, V as useNotificationStore, O as useDrawerStore, B as BUTTON_TEXTS, W as useEntityStore, A as ACTION_OPTIONS, g as getActionIcon, z as useModalStore, F as FORM_ALERTS, X as useFilterStore, D as DISPLAY_LANGUAGES, M as MONITORS_OPTIONS, d as getInstrumentationRuleIcon, Y as useDataStreamStore, Z as useInstrumentStore, c as getEntityId, S as STORAGE_KEYS, a as DEFAULT_DATA_STREAM_NAME, _ as useSetupStore, e as getProgrammingLanguageIcon, I as INSTRUMENTATION_RULE_OPTIONS, $ as useSelectedStore, j as ImageErrorIcon, a0 as useDarkMode } from './index-fdf3a268.js';
4
- import { ActionType, ActionKeyTypes, InputTypes, FieldTypes, EntityTypes, StatusType, Crud, OtherStatus, NodeTypes, EdgeTypes, AddNodeTypes, SignalType, HeadersCollectionKeyTypes, CustomInstrumentationsKeyTypes, CodeAttributesKeyTypes, PayloadCollectionKeyTypes, InstrumentationRuleType, K8sResourceKind, MountMethod, AgentEnvVarsInjectionMethod, Profile, InstallationMethod } from './types.js';
4
+ import { ActionType, ActionKeyTypes, InputTypes, FieldTypes, EntityTypes, StatusType, Crud, OtherStatus, NodeTypes, EdgeTypes, AddNodeTypes, SignalType, HeadersCollectionKeyTypes, CustomInstrumentationsKeyTypes, CodeAttributesKeyTypes, PayloadCollectionKeyTypes, InstrumentationRuleType, MountMethod, AgentEnvVarsInjectionMethod, Profile, InstallationMethod } from './types.js';
5
5
  import { e as DataCardFieldTypes, p as FieldLabel, C as Checkbox, o as FieldError, v as Input, x as InputTable, K as KeyValueInputsList, w as InputList, _ as Text, R as Segment, Q as SectionTitle, j as DocsButton, z as MonitorsCheckboxes, $ as TextArea, k as Drawer, c as ConditionDetails, D as DataCard, a5 as FlexColumn, M as Modal, N as NavigationButtons, a9 as ModalBody, L as NotificationNote, A as AutocompleteInput, i as Divider, W as Status, a4 as FlexRow, a1 as Tooltip, s as IconWrapped, G as MonitorsIcons, aa as TableContainer, ab as TableTitleWrap, r as IconTitleBadge, ac as TableWrap, y as InteractiveTable, a6 as CenterThis, J as NoDataFound, a2 as TraceLoader, a as Badge, E as ExtendArrow, a7 as VerticalScroll, U as SelectionButton, B as Button, n as Dropdown, ad as nodeConfig, ae as useNodesState, af as useEdgesState, ag as Flow, ah as applyNodeChanges, P as Popup, a0 as Toggle, I as IconButton, ai as AddButton, F as FadeLoader, g as DataTab, X as Stepper, d as DataCardFields, Z as Tag, aj as MarkerType, t as IconsNav, ak as CopyText, h as DescribeRow, al as PodContainer, am as SourceContainer, q as IconGroup, O as PopupForm } from './index-a1b59a38.js';
6
6
  import { i as isEmpty, s as safeJsonParse, d as deepClone } from './index-5e5f7bda.js';
7
7
  import { C as CheckCircledIcon, O as OdigosLogo } from './index-1d5c06a0.js';
@@ -4193,26 +4193,59 @@ const SlackInvite = ({}) => {
4193
4193
  React.createElement(SlackLogo, null)));
4194
4194
  };
4195
4195
 
4196
- const Describe$1 = ({ source, fetchDescribeSource }) => {
4196
+ const buildCard = (source) => {
4197
+ const { name, kind, namespace } = source;
4198
+ const arr = [
4199
+ { title: DISPLAY_TITLES.NAMESPACE, value: namespace },
4200
+ { title: DISPLAY_TITLES.KIND, value: kind },
4201
+ { title: DISPLAY_TITLES.NAME, value: name, tooltip: 'K8s resource name' },
4202
+ { type: DataCardFieldTypes.Divider },
4203
+ {
4204
+ type: DataCardFieldTypes.CopyText,
4205
+ value: `kubectl get ${kind} ${name} -n ${namespace}`.toLowerCase(),
4206
+ },
4207
+ ];
4208
+ return arr;
4209
+ };
4210
+
4211
+ const Container$2 = styled.div `
4212
+ display: flex;
4213
+ flex-direction: column;
4214
+ gap: 24px;
4215
+ padding: 4px;
4216
+ `;
4217
+ const SourceForm = ({ formData, handleFormChange }) => {
4218
+ return (React.createElement(Container$2, null,
4219
+ React.createElement(Input, { name: 'sourceName', title: 'Source name', tooltip: 'This overrides the default service name that runs in your cluster.', placeholder: 'Use a name that overrides the source name', value: formData.otelServiceName, onChange: ({ target: { value } }) => handleFormChange('otelServiceName', value) })));
4220
+ };
4221
+
4222
+ const Describe$1 = ({ source, fetchSourceDescribe }) => {
4223
+ const [failed, setFailed] = useState(false);
4197
4224
  const [describe, setDescribe] = useState(null);
4198
4225
  useEffect(() => {
4199
4226
  if (!source)
4200
4227
  return;
4201
- fetchDescribeSource({
4228
+ fetchSourceDescribe({
4202
4229
  variables: {
4203
4230
  namespace: source.namespace,
4204
4231
  name: source.name,
4205
4232
  kind: source.kind,
4206
4233
  },
4207
4234
  }).then(({ data }) => {
4208
- setDescribe(data?.describeSource || null);
4235
+ if (data?.describeSource) {
4236
+ setDescribe(data.describeSource);
4237
+ }
4238
+ else {
4239
+ setFailed(true);
4240
+ }
4209
4241
  });
4210
- }, [fetchDescribeSource, source]);
4211
- if (!describe) {
4242
+ }, [fetchSourceDescribe, source]);
4243
+ if (!describe && !failed) {
4212
4244
  return (React.createElement(CenterThis, null,
4213
4245
  React.createElement(FadeLoader, null)));
4214
4246
  }
4215
- return (React.createElement(FlexColumn, { "$gap": 12 }, !describe.pods?.length ? (React.createElement(CenterThis, null,
4247
+ return (React.createElement(FlexColumn, { "$gap": 12 }, failed ? (React.createElement(CenterThis, null,
4248
+ React.createElement(NoDataFound, { subTitle: 'Could not fetch describe for this source' }))) : !describe?.pods?.length ? (React.createElement(CenterThis, null,
4216
4249
  React.createElement(NoDataFound, { subTitle: 'Check if you have any running pods and try again' }))) : (describe.pods.map(({ podName, nodeName, phase, agentInjected, runningLatestWorkloadRevision, containers }) => {
4217
4250
  const podHasErrors = phase.status !== StatusType.Success || hasUnhealthyInstances(containers);
4218
4251
  const podStatus = podHasErrors ? StatusType.Error : StatusType.Success;
@@ -4248,36 +4281,65 @@ const Describe$1 = ({ source, fetchDescribeSource }) => {
4248
4281
  }))));
4249
4282
  };
4250
4283
 
4251
- const buildCard = (source) => {
4252
- const { name, kind, namespace } = source;
4253
- const arr = [
4254
- { title: DISPLAY_TITLES.NAMESPACE, value: namespace },
4255
- { title: DISPLAY_TITLES.KIND, value: kind },
4256
- { title: DISPLAY_TITLES.NAME, value: name, tooltip: 'K8s resource name' },
4257
- { type: DataCardFieldTypes.Divider },
4258
- {
4259
- type: DataCardFieldTypes.CopyText,
4260
- value: `kubectl get ${kind} ${name} -n ${namespace}`.toLowerCase(),
4261
- },
4262
- ];
4263
- return arr;
4264
- };
4265
-
4266
- const Container$2 = styled.div `
4267
- display: flex;
4268
- flex-direction: column;
4269
- gap: 24px;
4270
- padding: 4px;
4284
+ const Row = styled(FlexRow) `
4285
+ width: 100%;
4286
+ align-items: center;
4287
+ justify-content: space-between;
4271
4288
  `;
4272
- const SourceForm = ({ formData, handleFormChange }) => {
4273
- return (React.createElement(Container$2, null,
4274
- React.createElement(Input, { name: 'sourceName', title: 'Source name', tooltip: 'This overrides the default service name that runs in your cluster.', placeholder: 'Use a name that overrides the source name', value: formData.otelServiceName, onChange: ({ target: { value } }) => handleFormChange('otelServiceName', value) })));
4289
+ const Libraries = ({ source, fetchSourceLibraries }) => {
4290
+ const theme = Theme.useTheme();
4291
+ const [failed, setFailed] = useState(false);
4292
+ const [libraries, setLibraries] = useState(null);
4293
+ useEffect(() => {
4294
+ if (!source)
4295
+ return;
4296
+ fetchSourceLibraries({
4297
+ variables: {
4298
+ namespace: source.namespace,
4299
+ name: source.name,
4300
+ kind: source.kind,
4301
+ },
4302
+ }).then(({ data }) => {
4303
+ if (data?.instrumentationInstanceComponents) {
4304
+ setLibraries(data.instrumentationInstanceComponents);
4305
+ }
4306
+ else {
4307
+ setFailed(true);
4308
+ }
4309
+ });
4310
+ }, [fetchSourceLibraries, source]);
4311
+ if (!libraries && !failed) {
4312
+ return (React.createElement(CenterThis, null,
4313
+ React.createElement(FadeLoader, null)));
4314
+ }
4315
+ return (React.createElement(FlexColumn, { "$gap": 12 }, failed ? (React.createElement(CenterThis, null,
4316
+ React.createElement(NoDataFound, { subTitle: 'Could not fetch libraries for this source' }))) : !libraries?.length ? (React.createElement(CenterThis, { "$gap": 12 },
4317
+ React.createElement(NoDataFound, { title: 'No libraries found', subTitle: '' }),
4318
+ React.createElement(NotificationNote, { type: StatusType.Warning, message: 'This feature is in early development, and has very limited support' }))) : (React.createElement(DataCard, { title: 'Instrumented Libraries' }, libraries
4319
+ .sort((a, b) => a.name.localeCompare(b.name))
4320
+ .map(({ name, nonIdentifyingAttributes }, i) => (React.createElement(Fragment, { key: `library-${name}` },
4321
+ React.createElement(Row, null,
4322
+ React.createElement(Text, { size: 12, color: theme.text.grey }, name),
4323
+ React.createElement(FlexRow, { "$gap": 4 }, nonIdentifyingAttributes.map(({ key, value }) => {
4324
+ if (isStringABoolean(value) && parseBooleanFromString(value)) {
4325
+ switch (key) {
4326
+ case 'is_standard_lib':
4327
+ return React.createElement(Status, { key: `${name}-${key}`, status: StatusType.Default, title: 'STANDARD', withBorder: true });
4328
+ default:
4329
+ console.warn(`Unhandled non-identifying attribute: ${key}`);
4330
+ return null;
4331
+ }
4332
+ }
4333
+ return null;
4334
+ }))),
4335
+ i !== libraries.length - 1 && React.createElement(Divider, { length: '100%', margin: '0' }))))))));
4275
4336
  };
4276
4337
 
4277
4338
  var Tabs;
4278
4339
  (function (Tabs) {
4279
4340
  Tabs["Overview"] = "Overview";
4280
4341
  Tabs["Pods"] = "Pods";
4342
+ Tabs["Libraries"] = "Libraries";
4281
4343
  })(Tabs || (Tabs = {}));
4282
4344
  const FormContainer = styled.div `
4283
4345
  width: 100%;
@@ -4291,7 +4353,7 @@ const DataContainer$2 = styled.div `
4291
4353
  flex-direction: column;
4292
4354
  gap: 12px;
4293
4355
  `;
4294
- const SourceDrawer = ({ persistSources, updateSource, fetchDescribeSource, restartWorkloads }) => {
4356
+ const SourceDrawer = ({ persistSources, updateSource, restartWorkloads, fetchSourceById, fetchSourceDescribe, fetchSourceLibraries }) => {
4295
4357
  const { sources } = useEntityStore();
4296
4358
  const { selectedStreamName } = useDataStreamStore();
4297
4359
  const { drawerType, drawerEntityId } = useDrawerStore();
@@ -4301,15 +4363,23 @@ const SourceDrawer = ({ persistSources, updateSource, fetchDescribeSource, resta
4301
4363
  const [isEditing, setIsEditing] = useState(false);
4302
4364
  const [isFormDirty, setIsFormDirty] = useState(false);
4303
4365
  const [selectedTab, setSelectedTab] = useState(Tabs.Overview);
4366
+ const [fetchedSource, setFetchedSource] = useState(null);
4367
+ // this is used to fetch the source on drawer open, so we ensure we have the latest data
4368
+ useEffect(() => {
4369
+ if (!drawerEntityId)
4370
+ return;
4371
+ fetchSourceById(drawerEntityId).then((source) => setFetchedSource(source || null));
4372
+ }, [drawerEntityId]);
4304
4373
  const { formData, handleFormChange, resetFormData, loadFormWithDrawerItem } = useSourceFormData();
4305
4374
  const thisItem = useMemo(() => {
4306
4375
  if (isOpen)
4307
4376
  return null;
4308
- const found = sourcesByStream?.find((x) => x.namespace === drawerEntityId.namespace && x.name === drawerEntityId.name && x.kind === drawerEntityId.kind);
4377
+ const found = fetchedSource ||
4378
+ sourcesByStream?.find((x) => x.namespace === drawerEntityId.namespace && x.name === drawerEntityId.name && x.kind === drawerEntityId.kind);
4309
4379
  if (!!found)
4310
4380
  loadFormWithDrawerItem(found);
4311
4381
  return found;
4312
- }, [isOpen, drawerEntityId, sourcesByStream]);
4382
+ }, [isOpen, drawerEntityId, sourcesByStream, fetchedSource]);
4313
4383
  const containersData = useMemo(() => {
4314
4384
  const runtimeCondition = thisItem?.conditions?.find(({ type }) => type === 'RuntimeDetection');
4315
4385
  return {
@@ -4318,23 +4388,6 @@ const SourceDrawer = ({ persistSources, updateSource, fetchDescribeSource, resta
4318
4388
  containers: thisItem?.containers || [],
4319
4389
  };
4320
4390
  }, [thisItem]);
4321
- const tabs = useMemo(() => {
4322
- const arr = [
4323
- {
4324
- label: Tabs.Overview,
4325
- onClick: () => setSelectedTab(Tabs.Overview),
4326
- selected: selectedTab === Tabs.Overview,
4327
- },
4328
- ];
4329
- if (thisItem?.kind !== K8sResourceKind.CronJob) {
4330
- arr.push({
4331
- label: Tabs.Pods,
4332
- onClick: () => setSelectedTab(Tabs.Pods),
4333
- selected: selectedTab === Tabs.Pods,
4334
- });
4335
- }
4336
- return arr;
4337
- }, [thisItem?.kind, selectedTab]);
4338
4391
  if (!thisItem)
4339
4392
  return null;
4340
4393
  const handleEdit = (bool) => {
@@ -4361,7 +4414,23 @@ const SourceDrawer = ({ persistSources, updateSource, fetchDescribeSource, resta
4361
4414
  setIsFormDirty(false);
4362
4415
  setIsEditing(false);
4363
4416
  };
4364
- return (React.createElement(OverviewDrawer, { ref: drawerRef, title: thisItem.otelServiceName || thisItem.name, titleTooltip: 'This attribute is used to identify the name of the service (service.name) that is generating telemetry data.', hideEditTitleFromEdit: true, icons: getContainersIcons(thisItem.containers), isEdit: isEditing, isFormDirty: isFormDirty, onEdit: selectedTab === Tabs.Overview ? handleEdit : undefined, onSave: handleSave, onDelete: selectedTab === Tabs.Overview ? handleDelete : undefined, onCancel: handleCancel, isLastItem: sourcesByStream.length === 1, tabs: tabs, headerActionButtons: [
4417
+ return (React.createElement(OverviewDrawer, { ref: drawerRef, title: thisItem.otelServiceName || thisItem.name, titleTooltip: 'This attribute is used to identify the name of the service (service.name) that is generating telemetry data.', hideEditTitleFromEdit: true, icons: getContainersIcons(thisItem.containers), isEdit: isEditing, isFormDirty: isFormDirty, onEdit: selectedTab === Tabs.Overview ? handleEdit : undefined, onSave: handleSave, onDelete: selectedTab === Tabs.Overview ? handleDelete : undefined, onCancel: handleCancel, isLastItem: sourcesByStream.length === 1, tabs: [
4418
+ {
4419
+ label: Tabs.Overview,
4420
+ onClick: () => setSelectedTab(Tabs.Overview),
4421
+ selected: selectedTab === Tabs.Overview,
4422
+ },
4423
+ {
4424
+ label: Tabs.Pods,
4425
+ onClick: () => setSelectedTab(Tabs.Pods),
4426
+ selected: selectedTab === Tabs.Pods,
4427
+ },
4428
+ {
4429
+ label: Tabs.Libraries,
4430
+ onClick: () => setSelectedTab(Tabs.Libraries),
4431
+ selected: selectedTab === Tabs.Libraries,
4432
+ },
4433
+ ], headerActionButtons: [
4365
4434
  {
4366
4435
  'data-id': 'rollout-restart',
4367
4436
  variant: 'tertiary',
@@ -4378,7 +4447,7 @@ const SourceDrawer = ({ persistSources, updateSource, fetchDescribeSource, resta
4378
4447
  } }))) : (React.createElement(DataContainer$2, null,
4379
4448
  React.createElement(ConditionDetails, { conditions: thisItem.conditions || [] }),
4380
4449
  React.createElement(DataCard, { title: DISPLAY_TITLES.SOURCE_DETAILS, data: !!thisItem ? buildCard(thisItem) : [] }),
4381
- React.createElement(DataCard, { title: DISPLAY_TITLES.DETECTED_CONTAINERS, titleBadge: containersData.isLoading ? OtherStatus.Loading : containersData.containers.length, description: containersData.description || DISPLAY_TITLES.DETECTED_CONTAINERS_DESCRIPTION }, containersData.containers.map((container) => (React.createElement(SourceContainer, { key: `source-container-${container.containerName}`, ...container, callbackRuntimeOverride: async (payload) => await updateSource(drawerEntityId, payload) }))))))) : (React.createElement(Describe$1, { source: thisItem, fetchDescribeSource: fetchDescribeSource }))));
4450
+ React.createElement(DataCard, { title: DISPLAY_TITLES.DETECTED_CONTAINERS, titleBadge: containersData.isLoading ? OtherStatus.Loading : containersData.containers.length, description: containersData.description || DISPLAY_TITLES.DETECTED_CONTAINERS_DESCRIPTION }, containersData.containers.map((container) => (React.createElement(SourceContainer, { key: `source-container-${container.containerName}`, ...container, callbackRuntimeOverride: async (payload) => await updateSource(drawerEntityId, payload) }))))))) : selectedTab === Tabs.Pods ? (React.createElement(Describe$1, { source: thisItem, fetchSourceDescribe: fetchSourceDescribe })) : (React.createElement(Libraries, { source: thisItem, fetchSourceLibraries: fetchSourceLibraries }))));
4382
4451
  };
4383
4452
 
4384
4453
  const SearchWrapper = styled.div `
@@ -2,6 +2,7 @@ export * from './actions';
2
2
  export * from './config';
3
3
  export * from './describe';
4
4
  export * from './destinations';
5
+ export * from './instrumentation-instances';
5
6
  export * from './instrumentation-rules';
6
7
  export * from './namespaces';
7
8
  export * from './service-map';
@@ -0,0 +1,2 @@
1
+ import type { InstrumentationInstanceComponent } from '@/types';
2
+ export declare const MOCK_INSTRUMENTATION_INSTANCE_COMPONENTS: InstrumentationInstanceComponent[];
@@ -6,6 +6,7 @@ export * from './data-flow';
6
6
  export * from './data-streams';
7
7
  export * from './describe';
8
8
  export * from './destinations';
9
+ export * from './instrumentation-instances';
9
10
  export * from './instrumentation-rules';
10
11
  export * from './metrics';
11
12
  export * from './namespaces';
@@ -0,0 +1,7 @@
1
+ export interface InstrumentationInstanceComponent {
2
+ name: string;
3
+ nonIdentifyingAttributes: {
4
+ key: string;
5
+ value: string;
6
+ }[];
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@odigos/ui-kit",
3
- "version": "0.0.72",
3
+ "version": "0.0.73",
4
4
  "author": "Odigos",
5
5
  "repository": {
6
6
  "type": "git",