diff --git a/README.md b/README.md index 95afe4146..a6483bfc6 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ For more details, see our [quickstart guide](https://docs.odigos.io/intro). | Destination | Traces | Metrics | Logs | |-------------------------|:------:|:-------:|:----:| -| AppDynamics | ✅ | | | +| AppDynamics | ✅ | ✅ | ✅ | | Axiom | ✅ | | ✅ | | AWS S3 | ✅ | | ✅ | | Azure Blob Storage | ✅ | | ✅ | diff --git a/destinations/data/appdynamics.yaml b/destinations/data/appdynamics.yaml index 0c59c6ec6..2080f6092 100644 --- a/destinations/data/appdynamics.yaml +++ b/destinations/data/appdynamics.yaml @@ -10,9 +10,9 @@ spec: traces: supported: true metrics: - supported: false + supported: true logs: - supported: false + supported: true fields: - name: APPDYNAMICS_APPLICATION_NAME displayName: Application Name diff --git a/docs/backends-overview.mdx b/docs/backends-overview.mdx index 6ae47d8c6..903dba68b 100644 --- a/docs/backends-overview.mdx +++ b/docs/backends-overview.mdx @@ -6,7 +6,7 @@ Odigos has destinations for many observability backends. | Destination | Category | Traces | Metrics | Logs | |-------------------------|------------------|:------:|:-------:|:----:| -| AppDynamics | Managed | ✅ | | | +| AppDynamics | Managed | ✅ | ✅ | ✅ | | Axiom | Managed | ✅ | | ✅ | | AWS S3 | Managed | ✅ | | ✅ | | Azure Blob Storage | Managed | ✅ | | ✅ | diff --git a/docs/backends/appdynamics.mdx b/docs/backends/appdynamics.mdx index 08b9576af..e4d73e21c 100644 --- a/docs/backends/appdynamics.mdx +++ b/docs/backends/appdynamics.mdx @@ -57,6 +57,8 @@ spec: name: appdynamics-secret signals: - TRACES + - METRICS + - LOGS type: appdynamics --- diff --git a/docs/backends/axiom.mdx b/docs/backends/axiom.mdx new file mode 100644 index 000000000..94c8a0e09 --- /dev/null +++ b/docs/backends/axiom.mdx @@ -0,0 +1,62 @@ +--- +title: 'Axiom' +--- + +## Configuring Axiom Backend + +1. [Register](https://app.axiom.co/register)/[Login](https://app.axiom.co/login) to Axiom. +2. Navigate to the [Axiom Datasets](https://app.axiom.co/settings/datasets) page, create a dataset and copy it's name. +3. Navigate to the [Axiom API Tokens](https://app.axiom.co/settings/api-tokens) page, and generate a new API Token. + +- **AXIOM_DATASET** - Axiom Dataset Name from above (step 2). +- **AXIOM_API_TOKEN** - Axiom API Token from above (step 3). + +## Adding a destination to Odigos + +Odigos makes it simple to add and configure destinations, allowing you to select the specific signals [traces/logs/metrics] that you want to send to each destination. There are two primary methods for configuring destinations in Odigos: + +1. **Using the UI** + +- Use the [Odigos CLI](https://docs.odigos.io/cli/odigos_ui) to access the UI: + +```bash +odigos ui +``` + + +2. **Using kubernetes manifests** + +Save the YAML below to a file (e.g., `destination.yaml`) and apply it using `kubectl`: + +```bash +kubectl apply -f destination.yaml +``` + + +```yaml +apiVersion: odigos.io/v1alpha1 +kind: Destination +metadata: + name: axiom-example + namespace: odigos-system +spec: + data: + AXIOM_DATASET: + destinationName: axiom + secretRef: + name: axiom-secret + signals: + - TRACES + - LOGS + type: axiom + +--- +apiVersion: v1 +data: + AXIOM_API_TOKEN: +kind: Secret +metadata: + name: axiom-secret + namespace: odigos-system +type: Opaque +``` \ No newline at end of file diff --git a/docs/mint.json b/docs/mint.json index c30d9ac29..977e7d86b 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -202,6 +202,7 @@ "pages": [ "backends/appdynamics", "backends/s3", + "backends/axiom", "backends/azureblob", "backends/causely", "backends/chronosphere", diff --git a/frontend/webapp/utils/functions/formatters.ts b/frontend/webapp/utils/functions/formatters.ts deleted file mode 100644 index 39cf58e7f..000000000 --- a/frontend/webapp/utils/functions/formatters.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { ExportedSignals, OVERVIEW_ENTITY_TYPES, WorkloadId } from '@/types'; - -export const formatBytes = (bytes?: number) => { - if (!bytes) return '0 KB/s'; - - const sizes = ['Bytes/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s']; - const i = Math.floor(Math.log(bytes) / Math.log(1024)); - const value = bytes / Math.pow(1024, i); - - return `${value.toFixed(i === 0 ? 0 : 1)} ${sizes[i]}`; -}; - -export const extractMonitors = (exportedSignals: ExportedSignals) => { - const filtered = Object.keys(exportedSignals).filter((signal) => exportedSignals[signal] === true); - - return filtered; -}; - -export const getIdFromSseTarget = (target: string, type: OVERVIEW_ENTITY_TYPES) => { - switch (type) { - case OVERVIEW_ENTITY_TYPES.SOURCE: { - const id: WorkloadId = { - namespace: '', - name: '', - kind: '', - }; - - target.split('&').forEach((str) => { - const [key, value] = str.split('='); - id[key] = value; - }); - - return id; - } - - default: - return target as string; - } -}; - -export const getSseTargetFromId = (id: string | WorkloadId, type: OVERVIEW_ENTITY_TYPES) => { - switch (type) { - case OVERVIEW_ENTITY_TYPES.SOURCE: { - let target = ''; - - Object.entries(id as WorkloadId).forEach(([key, value]) => { - target += `${key}=${value}&`; - }); - - target.slice(0, -1); - - return target; - } - - default: - return id as string; - } -}; diff --git a/frontend/webapp/utils/functions/formatters/clean-object-empty-strings-values/index.ts b/frontend/webapp/utils/functions/formatters/clean-object-empty-strings-values/index.ts new file mode 100644 index 000000000..4127f7093 --- /dev/null +++ b/frontend/webapp/utils/functions/formatters/clean-object-empty-strings-values/index.ts @@ -0,0 +1,43 @@ +export function cleanObjectEmptyStringsValues(obj: Record): Record { + const cleanArray = (arr: any[]): any[] => + arr.filter((item) => { + if (typeof item === 'object' && item !== null) { + return item.key !== '' && item.value !== ''; + } + return item !== ''; + }); + + const cleanObject = (o: Record): Record => + Object.fromEntries( + Object.entries(o) + .filter(([key, value]) => key !== '' && value !== '') + .map(([key, value]) => { + if (Array.isArray(value)) return [key, cleanArray(value)]; + else if (typeof value === 'object' && value !== null) return [key, cleanObject(value)]; + return [key, value]; + }), + ); + + return Object.entries(obj).reduce((acc, [key, value]) => { + try { + const parsed = JSON.parse(value); + if (Array.isArray(parsed)) { + acc[key] = JSON.stringify(cleanArray(parsed)); + } else if (typeof parsed === 'object' && parsed !== null) { + acc[key] = JSON.stringify(cleanObject(parsed)); + } else { + acc[key] = value; + } + } catch (error) { + // Handle non-stringified objects or arrays directly + if (typeof value === 'object' && value !== null) { + if (Array.isArray(value)) acc[key] = JSON.stringify(cleanArray(value)); + else acc[key] = JSON.stringify(cleanObject(value)); + } else { + // In case JSON.parse fails, assume value is a plain string or non-object/array + acc[key] = value; + } + } + return acc; + }, {} as Record); +} diff --git a/frontend/webapp/utils/functions/formatters/extract-monitors/index.ts b/frontend/webapp/utils/functions/formatters/extract-monitors/index.ts new file mode 100644 index 000000000..e552ecfc3 --- /dev/null +++ b/frontend/webapp/utils/functions/formatters/extract-monitors/index.ts @@ -0,0 +1,7 @@ +import type { ExportedSignals } from '@/types'; + +export const extractMonitors = (exportedSignals: ExportedSignals) => { + const filtered = Object.keys(exportedSignals).filter((signal) => exportedSignals[signal] === true); + + return filtered; +}; diff --git a/frontend/webapp/utils/functions/formatters/format-bytes/index.ts b/frontend/webapp/utils/functions/formatters/format-bytes/index.ts new file mode 100644 index 000000000..33371ba21 --- /dev/null +++ b/frontend/webapp/utils/functions/formatters/format-bytes/index.ts @@ -0,0 +1,9 @@ +export const formatBytes = (bytes?: number) => { + if (!bytes) return '0 KB/s'; + + const sizes = ['Bytes/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s']; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + const value = bytes / Math.pow(1024, i); + + return `${value.toFixed(i === 0 ? 0 : 1)} ${sizes[i]}`; +}; diff --git a/frontend/webapp/utils/functions/formatters/get-id-from-sse-target/index.ts b/frontend/webapp/utils/functions/formatters/get-id-from-sse-target/index.ts new file mode 100644 index 000000000..b43acf298 --- /dev/null +++ b/frontend/webapp/utils/functions/formatters/get-id-from-sse-target/index.ts @@ -0,0 +1,23 @@ +import { OVERVIEW_ENTITY_TYPES, type WorkloadId } from '@/types'; + +export const getIdFromSseTarget = (target: string, type: OVERVIEW_ENTITY_TYPES) => { + switch (type) { + case OVERVIEW_ENTITY_TYPES.SOURCE: { + const id: WorkloadId = { + namespace: '', + name: '', + kind: '', + }; + + target.split('&').forEach((str) => { + const [key, value] = str.split('='); + id[key] = value; + }); + + return id; + } + + default: + return target as string; + } +}; diff --git a/frontend/webapp/utils/functions/formatters/get-sse-target-from-id/index.ts b/frontend/webapp/utils/functions/formatters/get-sse-target-from-id/index.ts new file mode 100644 index 000000000..063eb16a5 --- /dev/null +++ b/frontend/webapp/utils/functions/formatters/get-sse-target-from-id/index.ts @@ -0,0 +1,20 @@ +import { OVERVIEW_ENTITY_TYPES, type WorkloadId } from '@/types'; + +export const getSseTargetFromId = (id: string | WorkloadId, type: OVERVIEW_ENTITY_TYPES) => { + switch (type) { + case OVERVIEW_ENTITY_TYPES.SOURCE: { + let target = ''; + + Object.entries(id as WorkloadId).forEach(([key, value]) => { + target += `${key}=${value}&`; + }); + + target.slice(0, -1); + + return target; + } + + default: + return id as string; + } +}; diff --git a/frontend/webapp/utils/functions/formatters/index.ts b/frontend/webapp/utils/functions/formatters/index.ts new file mode 100644 index 000000000..60092f157 --- /dev/null +++ b/frontend/webapp/utils/functions/formatters/index.ts @@ -0,0 +1,8 @@ +export * from './clean-object-empty-strings-values'; +export * from './extract-monitors'; +export * from './format-bytes'; +export * from './get-id-from-sse-target'; +export * from './get-sse-target-from-id'; +export * from './parse-json-string-to-pretty-string'; +export * from './safe-json-parse'; +export * from './stringify-non-string-values'; diff --git a/frontend/webapp/utils/functions/formatters/parse-json-string-to-pretty-string/index.ts b/frontend/webapp/utils/functions/formatters/parse-json-string-to-pretty-string/index.ts new file mode 100644 index 000000000..fb0b5fd55 --- /dev/null +++ b/frontend/webapp/utils/functions/formatters/parse-json-string-to-pretty-string/index.ts @@ -0,0 +1,33 @@ +export const parseJsonStringToPrettyString = (value: string) => { + let str = ''; + + try { + const parsed = JSON.parse(value); + + // Handle arrays + if (Array.isArray(parsed)) { + str = parsed + .map((item) => { + if (typeof item === 'object' && item !== null) return `${item.key}: ${item.value}`; + else return item; + }) + .join(', '); + } + + // Handle objects (non-array JSON objects) + else if (typeof parsed === 'object' && parsed !== null) { + str = Object.entries(parsed) + .map(([key, val]) => `${key}: ${val}`) + .join(', '); + } + + // Should never reach this if it's a string (it will throw) + else { + str = value; + } + } catch (error) { + str = value; + } + + return str; +}; diff --git a/frontend/webapp/utils/functions/formatters/safe-json-parse/index.ts b/frontend/webapp/utils/functions/formatters/safe-json-parse/index.ts new file mode 100644 index 000000000..dccc8ea3b --- /dev/null +++ b/frontend/webapp/utils/functions/formatters/safe-json-parse/index.ts @@ -0,0 +1,10 @@ +export function safeJsonParse(str: string | undefined, fallback: T): T { + if (!str) return fallback; + try { + const parsed = JSON.parse(str) as T; + return parsed; + } catch (e) { + console.error('Error parsing JSON string:', e); + return fallback; + } +} diff --git a/frontend/webapp/utils/functions/formatters/stringify-non-string-values/index.ts b/frontend/webapp/utils/functions/formatters/stringify-non-string-values/index.ts new file mode 100644 index 000000000..5a59b9df9 --- /dev/null +++ b/frontend/webapp/utils/functions/formatters/stringify-non-string-values/index.ts @@ -0,0 +1,12 @@ +export function stringifyNonStringValues(obj: Record): Record { + return Object.entries(obj).reduce((acc, [key, value]) => { + // Check if the value is already a string + if (typeof value === 'string') { + acc[key] = value; + } else { + // If not, stringify the value + acc[key] = JSON.stringify(value); + } + return acc; + }, {} as Record); +} diff --git a/frontend/webapp/utils/functions/icons.ts b/frontend/webapp/utils/functions/icons.ts deleted file mode 100644 index baf2ab0ec..000000000 --- a/frontend/webapp/utils/functions/icons.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { WORKLOAD_PROGRAMMING_LANGUAGES } from '../constants'; -import { type ActionsType, type InstrumentationRuleType, type NotificationType, OVERVIEW_ENTITY_TYPES, SourceContainer } from '@/types'; - -const BRAND_ICON = '/brand/odigos-icon.svg'; - -export const getStatusIcon = (status?: NotificationType) => { - if (!status) return BRAND_ICON; - - switch (status) { - case 'success': - return '/icons/notification/success-icon.svg'; - case 'error': - return '/icons/notification/error-icon2.svg'; - case 'warning': - return '/icons/notification/warning-icon2.svg'; - case 'info': - return '/icons/common/info.svg'; - default: - return BRAND_ICON; - } -}; - -export const getEntityIcon = (type?: OVERVIEW_ENTITY_TYPES) => { - if (!type) return BRAND_ICON; - - return `/icons/overview/${type}s.svg`; -}; - -export const getRuleIcon = (type?: InstrumentationRuleType) => { - if (!type) return BRAND_ICON; - - const typeLowerCased = type.replaceAll('-', '').toLowerCase(); - - return `/icons/rules/${typeLowerCased}.svg`; -}; - -export const getActionIcon = (type?: ActionsType | 'sampler' | 'attributes') => { - if (!type) return BRAND_ICON; - - const typeLowerCased = type.toLowerCase(); - const isSampler = typeLowerCased.includes('sampler'); - const isAttributes = typeLowerCased === 'attributes'; - - const iconName = isSampler ? 'sampler' : isAttributes ? 'piimasking' : typeLowerCased; - - return `/icons/actions/${iconName}.svg`; -}; - -export const getProgrammingLanguageIcon = (language?: SourceContainer['language']) => { - if (!language) return BRAND_ICON; - - const BASE_URL = 'https://d1n7d4xz7fr8b4.cloudfront.net/'; - const LANGUAGES_LOGOS: Record = { - [WORKLOAD_PROGRAMMING_LANGUAGES.JAVA]: `${BASE_URL}java.svg`, - [WORKLOAD_PROGRAMMING_LANGUAGES.GO]: `${BASE_URL}go.svg`, - [WORKLOAD_PROGRAMMING_LANGUAGES.JAVASCRIPT]: `${BASE_URL}nodejs.svg`, - [WORKLOAD_PROGRAMMING_LANGUAGES.PYTHON]: `${BASE_URL}python.svg`, - [WORKLOAD_PROGRAMMING_LANGUAGES.DOTNET]: `${BASE_URL}dotnet.svg`, - [WORKLOAD_PROGRAMMING_LANGUAGES.MYSQL]: `${BASE_URL}mysql.svg`, - [WORKLOAD_PROGRAMMING_LANGUAGES.NGINX]: `${BASE_URL}nginx.svg`, - [WORKLOAD_PROGRAMMING_LANGUAGES.IGNORED]: BRAND_ICON, // TODO: good icon - [WORKLOAD_PROGRAMMING_LANGUAGES.UNKNOWN]: BRAND_ICON, // TODO: good icon - [WORKLOAD_PROGRAMMING_LANGUAGES.PROCESSING]: BRAND_ICON, // TODO: good icon - [WORKLOAD_PROGRAMMING_LANGUAGES.NO_CONTAINERS]: BRAND_ICON, // TODO: good icon - [WORKLOAD_PROGRAMMING_LANGUAGES.NO_RUNNING_PODS]: BRAND_ICON, // TODO: good icon - }; - - return LANGUAGES_LOGOS[language] || BRAND_ICON; -}; diff --git a/frontend/webapp/utils/functions/icons/get-action-icon/index.ts b/frontend/webapp/utils/functions/icons/get-action-icon/index.ts new file mode 100644 index 000000000..4f95cc8a1 --- /dev/null +++ b/frontend/webapp/utils/functions/icons/get-action-icon/index.ts @@ -0,0 +1,15 @@ +import { type ActionsType } from '@/types'; + +const BRAND_ICON = '/brand/odigos-icon.svg'; + +export const getActionIcon = (type?: ActionsType | 'sampler' | 'attributes') => { + if (!type) return BRAND_ICON; + + const typeLowerCased = type.toLowerCase(); + const isSampler = typeLowerCased.includes('sampler'); + const isAttributes = typeLowerCased === 'attributes'; + + const iconName = isSampler ? 'sampler' : isAttributes ? 'piimasking' : typeLowerCased; + + return `/icons/actions/${iconName}.svg`; +}; diff --git a/frontend/webapp/utils/functions/icons/get-entity-icon/index.ts b/frontend/webapp/utils/functions/icons/get-entity-icon/index.ts new file mode 100644 index 000000000..3ddfeac9b --- /dev/null +++ b/frontend/webapp/utils/functions/icons/get-entity-icon/index.ts @@ -0,0 +1,9 @@ +import { OVERVIEW_ENTITY_TYPES } from '@/types'; + +const BRAND_ICON = '/brand/odigos-icon.svg'; + +export const getEntityIcon = (type?: OVERVIEW_ENTITY_TYPES) => { + if (!type) return BRAND_ICON; + + return `/icons/overview/${type}s.svg`; +}; diff --git a/frontend/webapp/utils/functions/icons/get-programming-language-icon/index.ts b/frontend/webapp/utils/functions/icons/get-programming-language-icon/index.ts new file mode 100644 index 000000000..2e3e449c3 --- /dev/null +++ b/frontend/webapp/utils/functions/icons/get-programming-language-icon/index.ts @@ -0,0 +1,26 @@ +import { WORKLOAD_PROGRAMMING_LANGUAGES } from '@/utils'; +import { type SourceContainer } from '@/types'; + +const BRAND_ICON = '/brand/odigos-icon.svg'; + +export const getProgrammingLanguageIcon = (language?: SourceContainer['language']) => { + if (!language) return BRAND_ICON; + + const BASE_URL = 'https://d1n7d4xz7fr8b4.cloudfront.net/'; + const LANGUAGES_LOGOS: Record = { + [WORKLOAD_PROGRAMMING_LANGUAGES.JAVA]: `${BASE_URL}java.svg`, + [WORKLOAD_PROGRAMMING_LANGUAGES.GO]: `${BASE_URL}go.svg`, + [WORKLOAD_PROGRAMMING_LANGUAGES.JAVASCRIPT]: `${BASE_URL}nodejs.svg`, + [WORKLOAD_PROGRAMMING_LANGUAGES.PYTHON]: `${BASE_URL}python.svg`, + [WORKLOAD_PROGRAMMING_LANGUAGES.DOTNET]: `${BASE_URL}dotnet.svg`, + [WORKLOAD_PROGRAMMING_LANGUAGES.MYSQL]: `${BASE_URL}mysql.svg`, + [WORKLOAD_PROGRAMMING_LANGUAGES.NGINX]: `${BASE_URL}nginx.svg`, + [WORKLOAD_PROGRAMMING_LANGUAGES.IGNORED]: BRAND_ICON, // TODO: good icon + [WORKLOAD_PROGRAMMING_LANGUAGES.UNKNOWN]: BRAND_ICON, // TODO: good icon + [WORKLOAD_PROGRAMMING_LANGUAGES.PROCESSING]: BRAND_ICON, // TODO: good icon + [WORKLOAD_PROGRAMMING_LANGUAGES.NO_CONTAINERS]: BRAND_ICON, // TODO: good icon + [WORKLOAD_PROGRAMMING_LANGUAGES.NO_RUNNING_PODS]: BRAND_ICON, // TODO: good icon + }; + + return LANGUAGES_LOGOS[language] || BRAND_ICON; +}; diff --git a/frontend/webapp/utils/functions/icons/get-rule-icon/index.ts b/frontend/webapp/utils/functions/icons/get-rule-icon/index.ts new file mode 100644 index 000000000..bfb23e0f0 --- /dev/null +++ b/frontend/webapp/utils/functions/icons/get-rule-icon/index.ts @@ -0,0 +1,11 @@ +import { type InstrumentationRuleType } from '@/types'; + +const BRAND_ICON = '/brand/odigos-icon.svg'; + +export const getRuleIcon = (type?: InstrumentationRuleType) => { + if (!type) return BRAND_ICON; + + const typeLowerCased = type.replaceAll('-', '').toLowerCase(); + + return `/icons/rules/${typeLowerCased}.svg`; +}; diff --git a/frontend/webapp/utils/functions/icons/get-status-icon/index.ts b/frontend/webapp/utils/functions/icons/get-status-icon/index.ts new file mode 100644 index 000000000..8f7c8d96d --- /dev/null +++ b/frontend/webapp/utils/functions/icons/get-status-icon/index.ts @@ -0,0 +1,20 @@ +import { type NotificationType } from '@/types'; + +const BRAND_ICON = '/brand/odigos-icon.svg'; + +export const getStatusIcon = (status?: NotificationType) => { + if (!status) return BRAND_ICON; + + switch (status) { + case 'success': + return '/icons/notification/success-icon.svg'; + case 'error': + return '/icons/notification/error-icon2.svg'; + case 'warning': + return '/icons/notification/warning-icon2.svg'; + case 'info': + return '/icons/common/info.svg'; + default: + return BRAND_ICON; + } +}; diff --git a/frontend/webapp/utils/functions/icons/index.ts b/frontend/webapp/utils/functions/icons/index.ts new file mode 100644 index 000000000..ad4fb6a94 --- /dev/null +++ b/frontend/webapp/utils/functions/icons/index.ts @@ -0,0 +1,5 @@ +export * from './get-action-icon'; +export * from './get-entity-icon'; +export * from './get-programming-language-icon'; +export * from './get-rule-icon'; +export * from './get-status-icon'; diff --git a/frontend/webapp/utils/functions/index.ts b/frontend/webapp/utils/functions/index.ts index 57838fb20..dcefed233 100644 --- a/frontend/webapp/utils/functions/index.ts +++ b/frontend/webapp/utils/functions/index.ts @@ -1,7 +1,4 @@ -export * from './derive-types'; export * from './formatters'; -export * from './get-entity-label'; -export * from './get-health-status'; -export * from './get-value-for-range'; export * from './icons'; +export * from './resolvers'; export * from './strings'; diff --git a/frontend/webapp/utils/functions/get-value-for-range.ts b/frontend/webapp/utils/functions/resolvers/get-value-for-range/index.ts similarity index 100% rename from frontend/webapp/utils/functions/get-value-for-range.ts rename to frontend/webapp/utils/functions/resolvers/get-value-for-range/index.ts diff --git a/frontend/webapp/utils/functions/resolvers/index.ts b/frontend/webapp/utils/functions/resolvers/index.ts new file mode 100644 index 000000000..5115af1ea --- /dev/null +++ b/frontend/webapp/utils/functions/resolvers/index.ts @@ -0,0 +1 @@ +export * from './get-value-for-range'; diff --git a/frontend/webapp/utils/functions/strings.tsx b/frontend/webapp/utils/functions/strings.tsx deleted file mode 100644 index 27c402a87..000000000 --- a/frontend/webapp/utils/functions/strings.tsx +++ /dev/null @@ -1,154 +0,0 @@ -export function capitalizeFirstLetter(string: string) { - return string.charAt(0).toUpperCase() + string.slice(1); -} - -export function safeJsonParse(str: string | undefined, fallback: T): T { - if (!str) return fallback; - try { - const parsed = JSON.parse(str) as T; - return parsed; - } catch (e) { - console.error('Error parsing JSON string:', e); - return fallback; - } -} - -export function cleanObjectEmptyStringsValues(obj: Record): Record { - const cleanArray = (arr: any[]): any[] => - arr.filter((item) => { - if (typeof item === 'object' && item !== null) { - return item.key !== '' && item.value !== ''; - } - return item !== ''; - }); - - const cleanObject = (o: Record): Record => - Object.fromEntries( - Object.entries(o) - .filter(([key, value]) => key !== '' && value !== '') - .map(([key, value]) => { - if (Array.isArray(value)) return [key, cleanArray(value)]; - else if (typeof value === 'object' && value !== null) return [key, cleanObject(value)]; - return [key, value]; - }), - ); - - return Object.entries(obj).reduce((acc, [key, value]) => { - try { - const parsed = JSON.parse(value); - if (Array.isArray(parsed)) { - acc[key] = JSON.stringify(cleanArray(parsed)); - } else if (typeof parsed === 'object' && parsed !== null) { - acc[key] = JSON.stringify(cleanObject(parsed)); - } else { - acc[key] = value; - } - } catch (error) { - // Handle non-stringified objects or arrays directly - if (typeof value === 'object' && value !== null) { - if (Array.isArray(value)) acc[key] = JSON.stringify(cleanArray(value)); - else acc[key] = JSON.stringify(cleanObject(value)); - } else { - // In case JSON.parse fails, assume value is a plain string or non-object/array - acc[key] = value; - } - } - return acc; - }, {} as Record); -} - -export function stringifyNonStringValues(obj: Record): Record { - return Object.entries(obj).reduce((acc, [key, value]) => { - // Check if the value is already a string - if (typeof value === 'string') { - acc[key] = value; - } else { - // If not, stringify the value - acc[key] = JSON.stringify(value); - } - return acc; - }, {} as Record); -} - -export const parseJsonStringToPrettyString = (value: string) => { - let str = ''; - - try { - const parsed = JSON.parse(value); - - // Handle arrays - if (Array.isArray(parsed)) { - str = parsed - .map((item) => { - if (typeof item === 'object' && item !== null) return `${item.key}: ${item.value}`; - else return item; - }) - .join(', '); - } - - // Handle objects (non-array JSON objects) - else if (typeof parsed === 'object' && parsed !== null) { - str = Object.entries(parsed) - .map(([key, val]) => `${key}: ${val}`) - .join(', '); - } - - // Should never reach this if it's a string (it will throw) - else { - str = value; - } - } catch (error) { - str = value; - } - - return str; -}; - -export const timeAgo = (timestamp: string) => { - const now = new Date(); - const notificationTime = new Date(timestamp); - - if (isNaN(notificationTime.getTime())) { - return ''; - } - - const differenceInMs = now.getTime() - notificationTime.getTime(); - const differenceInMinutes = Math.floor(differenceInMs / (1000 * 60)); - const differenceInHours = Math.floor(differenceInMinutes / 60); - - if (differenceInMinutes < 60) { - if (differenceInMinutes === 0) { - return 'Just now'; - } else if (differenceInMinutes === 1) { - return '1 minute ago'; - } - - return `${differenceInMinutes} minutes ago`; - } else if (differenceInHours < 24) { - return `${differenceInHours} hours ago`; - } else { - const days = Math.floor(differenceInHours / 24); - return `${days} days ago`; - } -}; - -export function formatDate(dateString: string) { - // Parse the date string into a Date object - const date = new Date(dateString); - - // Get individual components - const year = date.getUTCFullYear(); - const month = date.getUTCMonth(); // Note: months are zero-based - const day = date.getUTCDate(); - const hours = date.getUTCHours(); - const minutes = date.getUTCMinutes(); - const seconds = date.getUTCSeconds(); - - // Define month names - const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; - - // Format the components into a readable string - const formattedDate = `${monthNames[month]} ${day}, ${year} ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; - - return formattedDate; -} diff --git a/frontend/webapp/utils/functions/strings/capitalize-first-letter/index.ts b/frontend/webapp/utils/functions/strings/capitalize-first-letter/index.ts new file mode 100644 index 000000000..2d08599c2 --- /dev/null +++ b/frontend/webapp/utils/functions/strings/capitalize-first-letter/index.ts @@ -0,0 +1,3 @@ +export function capitalizeFirstLetter(string: string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} diff --git a/frontend/webapp/utils/functions/derive-types.ts b/frontend/webapp/utils/functions/strings/derive-type-from-rule/index.ts similarity index 90% rename from frontend/webapp/utils/functions/derive-types.ts rename to frontend/webapp/utils/functions/strings/derive-type-from-rule/index.ts index 9763f2f55..30051146d 100644 --- a/frontend/webapp/utils/functions/derive-types.ts +++ b/frontend/webapp/utils/functions/strings/derive-type-from-rule/index.ts @@ -7,5 +7,3 @@ export const deriveTypeFromRule = (rule: InstrumentationRuleInput | Instrumentat return undefined; }; - -// TODO: add "deriveTypeFromAction" diff --git a/frontend/webapp/utils/functions/get-entity-label.ts b/frontend/webapp/utils/functions/strings/get-entity-label/index.ts similarity index 88% rename from frontend/webapp/utils/functions/get-entity-label.ts rename to frontend/webapp/utils/functions/strings/get-entity-label/index.ts index 964eca7de..930bad81e 100644 --- a/frontend/webapp/utils/functions/get-entity-label.ts +++ b/frontend/webapp/utils/functions/strings/get-entity-label/index.ts @@ -1,4 +1,4 @@ -import { ActionData, ActionDataParsed, ActualDestination, InstrumentationRuleSpec, K8sActualSource, OVERVIEW_ENTITY_TYPES } from '@/types'; +import { type ActionData, type ActionDataParsed, type ActualDestination, type InstrumentationRuleSpec, type K8sActualSource, OVERVIEW_ENTITY_TYPES } from '@/types'; export const getEntityLabel = ( entity: InstrumentationRuleSpec | K8sActualSource | ActionData | ActualDestination, diff --git a/frontend/webapp/utils/functions/get-health-status.ts b/frontend/webapp/utils/functions/strings/get-health-status/index.ts similarity index 90% rename from frontend/webapp/utils/functions/get-health-status.ts rename to frontend/webapp/utils/functions/strings/get-health-status/index.ts index 0de6fe6f1..247fc09d1 100644 --- a/frontend/webapp/utils/functions/get-health-status.ts +++ b/frontend/webapp/utils/functions/strings/get-health-status/index.ts @@ -1,4 +1,4 @@ -import { BACKEND_BOOLEAN } from '../constants'; +import { BACKEND_BOOLEAN } from '@/utils'; import { STATUSES, type ActualDestination, type K8sActualSource } from '@/types'; export const getHealthStatus = (item: K8sActualSource | ActualDestination) => { diff --git a/frontend/webapp/utils/functions/strings/index.ts b/frontend/webapp/utils/functions/strings/index.ts new file mode 100644 index 000000000..d92aa67ad --- /dev/null +++ b/frontend/webapp/utils/functions/strings/index.ts @@ -0,0 +1,4 @@ +export * from './capitalize-first-letter'; +export * from './derive-type-from-rule'; +export * from './get-entity-label'; +export * from './get-health-status';