From 2ce5948aac210a33330815909b19b80a425f7bf0 Mon Sep 17 00:00:00 2001 From: Alon Braymok <138359965+alonkeyval@users.noreply.github.com> Date: Wed, 10 Jul 2024 17:07:29 +0300 Subject: [PATCH] [TASK-1010] probabilistic and error samplers (#1349) This PR adds two new Odigos actions that allow the user to sample their data. --- frontend/endpoints/actions/errorsampler.go | 100 ++++++++++++++++++ .../endpoints/actions/probabilisticsampler.go | 2 +- frontend/endpoints/actions/root.go | 33 ++++++ frontend/main.go | 11 ++ .../(actions)/choose-action/page.tsx | 2 +- .../components/common/multi.checkbox.tsx | 6 +- .../overview/actions/action.icon/index.tsx | 6 ++ .../dynamic.action.form/index.tsx | 5 + .../samplers/error-sampler/index.tsx | 48 +++++++++ .../actions/actions.forms/samplers/index.ts | 2 + .../samplers/probabilistic-sampler/index.tsx | 50 +++++++++ .../action.row.dynamic.content/index.tsx | 12 +++ .../actions.table/actions.table.row.tsx | 11 +- .../overview/actions/delete.action/index.tsx | 2 +- .../actions/new.action.card/index.tsx | 2 +- .../main/actions/choose-action/index.tsx | 14 +++ .../main/actions/create-action/index.tsx | 15 ++- .../main/actions/edit-action/index.tsx | 5 +- frontend/webapp/design.system/input/input.tsx | 4 + .../webapp/hooks/actions/useActionState.ts | 28 ++++- frontend/webapp/package.json | 2 +- frontend/webapp/types/actions.ts | 2 + frontend/webapp/utils/constants/string.tsx | 8 ++ frontend/webapp/yarn.lock | 8 +- 24 files changed, 347 insertions(+), 31 deletions(-) create mode 100644 frontend/endpoints/actions/errorsampler.go create mode 100644 frontend/webapp/components/overview/actions/actions.forms/samplers/error-sampler/index.tsx create mode 100644 frontend/webapp/components/overview/actions/actions.forms/samplers/index.ts create mode 100644 frontend/webapp/components/overview/actions/actions.forms/samplers/probabilistic-sampler/index.tsx diff --git a/frontend/endpoints/actions/errorsampler.go b/frontend/endpoints/actions/errorsampler.go new file mode 100644 index 000000000..5d4d032c8 --- /dev/null +++ b/frontend/endpoints/actions/errorsampler.go @@ -0,0 +1,100 @@ +package actions + +import ( + "github.com/gin-gonic/gin" + "github.com/odigos-io/odigos/api/actions/v1alpha1" + "github.com/odigos-io/odigos/frontend/kube" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func GetErrorSampler(c *gin.Context, odigosns string, id string) { + action, err := kube.DefaultClient.ActionsClient.ErrorSamplers(odigosns).Get(c, id, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + c.JSON(404, gin.H{ + "error": "not found", + }) + return + } else { + c.JSON(500, gin.H{ + "error": err.Error(), + }) + return + } + } + c.JSON(200, action.Spec) +} + +func CreateErrorSampler(c *gin.Context, odigosns string) { + var action v1alpha1.ErrorSampler + if err := c.ShouldBindJSON(&action.Spec); err != nil { + c.JSON(400, gin.H{ + "error": err.Error(), + }) + return + } + action.GenerateName = "es-" + generatedAction, err := kube.DefaultClient.ActionsClient.ErrorSamplers(odigosns).Create(c, &action, metav1.CreateOptions{}) + if err != nil { + c.JSON(500, gin.H{ + "error": err.Error(), + }) + return + } + c.JSON(201, gin.H{ + "id": generatedAction.Name, + }) +} + +func UpdateErrorSampler(c *gin.Context, odigosns string, id string) { + action, err := kube.DefaultClient.ActionsClient.ErrorSamplers(odigosns).Get(c, id, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + c.JSON(404, gin.H{ + "error": "not found", + }) + return + } else { + c.JSON(500, gin.H{ + "error": err.Error(), + }) + } + return + } + action.Spec = v1alpha1.ErrorSamplerSpec{} + if err := c.ShouldBindJSON(&action.Spec); err != nil { + c.JSON(400, gin.H{ + "error": err.Error(), + }) + return + } + action.Name = id + + _, err = kube.DefaultClient.ActionsClient.ErrorSamplers(odigosns).Update(c, action, metav1.UpdateOptions{}) + if err != nil { + c.JSON(500, gin.H{ + "error": err.Error(), + }) + return + } + c.JSON(204, nil) +} + +func DeleteErrorSampler(c *gin.Context, odigosns string, id string) { + err := kube.DefaultClient.ActionsClient.ErrorSamplers(odigosns).Delete(c, id, metav1.DeleteOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + c.JSON(404, gin.H{ + "error": "not found", + }) + return + } else { + c.JSON(500, gin.H{ + "error": err.Error(), + }) + return + } + } + c.JSON(204, nil) +} diff --git a/frontend/endpoints/actions/probabilisticsampler.go b/frontend/endpoints/actions/probabilisticsampler.go index f290112ca..6a1758f54 100644 --- a/frontend/endpoints/actions/probabilisticsampler.go +++ b/frontend/endpoints/actions/probabilisticsampler.go @@ -34,7 +34,7 @@ func CreateProbabilisticSampler(c *gin.Context, odigosns string) { }) return } - action.GenerateName = "da-" + action.GenerateName = "ps-" generatedAction, err := kube.DefaultClient.ActionsClient.ProbabilisticSamplers(odigosns).Create(c, &action, metav1.CreateOptions{}) if err != nil { c.JSON(500, gin.H{ diff --git a/frontend/endpoints/actions/root.go b/frontend/endpoints/actions/root.go index e32c2f28e..8a63b1d8c 100644 --- a/frontend/endpoints/actions/root.go +++ b/frontend/endpoints/actions/root.go @@ -64,7 +64,24 @@ func GetActions(c *gin.Context, odigosns string) { }) } + esActions, err := kube.DefaultClient.ActionsClient.ErrorSamplers(odigosns).List(c, metav1.ListOptions{}) + if err != nil { + c.JSON(500, gin.H{ + "error": err.Error(), + }) + return + } + + for _, action := range esActions.Items { + response = append(response, IcaInstanceResponse{ + Id: action.Name, + Type: action.Kind, + Spec: action.Spec, + }) + } + lsActions, err := kube.DefaultClient.ActionsClient.LatencySamplers(odigosns).List(c, metav1.ListOptions{}) + if err != nil { c.JSON(500, gin.H{ "error": err.Error(), @@ -80,5 +97,21 @@ func GetActions(c *gin.Context, odigosns string) { }) } + psActions, err := kube.DefaultClient.ActionsClient.ProbabilisticSamplers(odigosns).List(c, metav1.ListOptions{}) + if err != nil { + c.JSON(500, gin.H{ + "error": err.Error(), + }) + return + } + + for _, action := range psActions.Items { + response = append(response, IcaInstanceResponse{ + Id: action.Name, + Type: action.Kind, + Spec: action.Spec, + }) + } + c.JSON(200, response) } diff --git a/frontend/main.go b/frontend/main.go index 63f01efb7..35887ac0d 100644 --- a/frontend/main.go +++ b/frontend/main.go @@ -138,11 +138,22 @@ func startHTTPServer(flags *Flags) (*gin.Engine, error) { apis.PUT("/actions/types/RenameAttribute/:id", func(c *gin.Context) { actions.UpdateRenameAttribute(c, flags.Namespace, c.Param("id")) }) apis.DELETE("/actions/types/RenameAttribute/:id", func(c *gin.Context) { actions.DeleteRenameAttribute(c, flags.Namespace, c.Param("id")) }) + // ErrorSampler + apis.GET("/actions/types/ErrorSampler/:id", func(c *gin.Context) { actions.GetErrorSampler(c, flags.Namespace, c.Param("id")) }) + apis.POST("/actions/types/ErrorSampler", func(c *gin.Context) { actions.CreateErrorSampler(c, flags.Namespace) }) + apis.PUT("/actions/types/ErrorSampler/:id", func(c *gin.Context) { actions.UpdateErrorSampler(c, flags.Namespace, c.Param("id")) }) + apis.DELETE("/actions/types/ErrorSampler/:id", func(c *gin.Context) { actions.DeleteErrorSampler(c, flags.Namespace, c.Param("id")) }) // LatencySampler apis.GET("/actions/types/LatencySampler/:id", func(c *gin.Context) { actions.GetLatencySampler(c, flags.Namespace, c.Param("id")) }) apis.POST("/actions/types/LatencySampler", func(c *gin.Context) { actions.CreateLatencySampler(c, flags.Namespace) }) apis.PUT("/actions/types/LatencySampler/:id", func(c *gin.Context) { actions.UpdateLatencySampler(c, flags.Namespace, c.Param("id")) }) apis.DELETE("/actions/types/LatencySampler/:id", func(c *gin.Context) { actions.DeleteLatencySampler(c, flags.Namespace, c.Param("id")) }) + + //ProbabilisticSampler + apis.GET("/actions/types/ProbabilisticSampler/:id", func(c *gin.Context) { actions.GetProbabilisticSampler(c, flags.Namespace, c.Param("id")) }) + apis.POST("/actions/types/ProbabilisticSampler", func(c *gin.Context) { actions.CreateProbabilisticSampler(c, flags.Namespace) }) + apis.PUT("/actions/types/ProbabilisticSampler/:id", func(c *gin.Context) { actions.UpdateProbabilisticSampler(c, flags.Namespace, c.Param("id")) }) + apis.DELETE("/actions/types/ProbabilisticSampler/:id", func(c *gin.Context) { actions.DeleteProbabilisticSampler(c, flags.Namespace, c.Param("id")) }) } return r, nil diff --git a/frontend/webapp/app/(overview)/(actions)/choose-action/page.tsx b/frontend/webapp/app/(overview)/(actions)/choose-action/page.tsx index f019d6c54..c193b1d6e 100644 --- a/frontend/webapp/app/(overview)/(actions)/choose-action/page.tsx +++ b/frontend/webapp/app/(overview)/(actions)/choose-action/page.tsx @@ -1,9 +1,9 @@ 'use client'; import React from 'react'; import { OVERVIEW } from '@/utils'; +import { useRouter } from 'next/navigation'; import { OverviewHeader } from '@/components'; import { ChooseActionContainer } from '@/containers'; -import { useRouter } from 'next/navigation'; export default function ChooseActionPage() { const router = useRouter(); diff --git a/frontend/webapp/components/common/multi.checkbox.tsx b/frontend/webapp/components/common/multi.checkbox.tsx index 589e7b343..d341734c7 100644 --- a/frontend/webapp/components/common/multi.checkbox.tsx +++ b/frontend/webapp/components/common/multi.checkbox.tsx @@ -1,5 +1,5 @@ import { KeyvalCheckbox, KeyvalText } from '@/design.system'; -import React, { useState } from 'react'; +import React, { use, useEffect, useState } from 'react'; import styled from 'styled-components'; interface CheckboxItem { @@ -28,6 +28,10 @@ export const MultiCheckboxComponent: React.FC = ({ const [selectedMonitors, setSelectedMonitors] = useState(checkboxes); + useEffect(() => { + checkboxes.length === 1 && setIsCheckboxDisabled(true); + }, [checkboxes]); + const handleCheckboxChange = (id: string) => { // Calculate the number of currently checked checkboxes const currentlyCheckedCount = selectedMonitors.filter( diff --git a/frontend/webapp/components/overview/actions/action.icon/index.tsx b/frontend/webapp/components/overview/actions/action.icon/index.tsx index f8b7b1276..01b6c3184 100644 --- a/frontend/webapp/components/overview/actions/action.icon/index.tsx +++ b/frontend/webapp/components/overview/actions/action.icon/index.tsx @@ -3,7 +3,9 @@ import { ActionsType } from '@/types'; import { AddClusterInfoIcon, DeleteAttributeIcon, + ErrorSamplerIcon, RenameAttributeIcon, + ProbabilisticSamplerIcon, } from '@keyval-dev/design-system'; export function ActionIcon({ type, ...props }) { @@ -14,6 +16,10 @@ export function ActionIcon({ type, ...props }) { return ; case ActionsType.DELETE_ATTRIBUTES: return ; + case ActionsType.ERROR_SAMPLER: + return ; + case ActionsType.PROBABILISTIC_SAMPLER: + return ; default: return null; } diff --git a/frontend/webapp/components/overview/actions/actions.forms/dynamic.action.form/index.tsx b/frontend/webapp/components/overview/actions/actions.forms/dynamic.action.form/index.tsx index 1c8bee301..edcb20b97 100644 --- a/frontend/webapp/components/overview/actions/actions.forms/dynamic.action.form/index.tsx +++ b/frontend/webapp/components/overview/actions/actions.forms/dynamic.action.form/index.tsx @@ -4,6 +4,7 @@ import { ActionsType } from '@/types'; import { AddClusterInfoForm } from '../add.cluster.info'; import { DeleteAttributesForm } from '../delete.attribute'; import { RenameAttributesForm } from '../rename.attributes'; +import { ErrorSamplerForm, ProbabilisticSamplerForm } from '../samplers'; interface DynamicActionFormProps { type: string | undefined; @@ -24,6 +25,10 @@ export function DynamicActionForm({ return ; case ActionsType.RENAME_ATTRIBUTES: return ; + case ActionsType.ERROR_SAMPLER: + return ; + case ActionsType.PROBABILISTIC_SAMPLER: + return ; default: return
; } diff --git a/frontend/webapp/components/overview/actions/actions.forms/samplers/error-sampler/index.tsx b/frontend/webapp/components/overview/actions/actions.forms/samplers/error-sampler/index.tsx new file mode 100644 index 000000000..3c71a61dd --- /dev/null +++ b/frontend/webapp/components/overview/actions/actions.forms/samplers/error-sampler/index.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import styled from 'styled-components'; +import { KeyvalInput } from '@/design.system'; + +const FormWrapper = styled.div` + width: 375px; +`; + +interface ErrorSampler { + fallback_sampling_ratio: number; +} + +interface ErrorSamplerFormProps { + data: ErrorSampler; + onChange: (key: string, value: ErrorSampler | null) => void; +} +const ACTION_DATA_KEY = 'actionData'; +export function ErrorSamplerForm({ + data, + onChange, +}: ErrorSamplerFormProps): React.JSX.Element { + function handleOnChange(fallback_sampling_ratio: number): void { + onChange(ACTION_DATA_KEY, { + fallback_sampling_ratio, + }); + } + + return ( + <> + + handleOnChange(+value)} + type="number" + tooltip="Specifies the ratio of non-error traces you still want to retain" + min={0} + max={100} + error={ + data?.fallback_sampling_ratio > 100 + ? 'Value must be less than 100' + : '' + } + /> + + + ); +} diff --git a/frontend/webapp/components/overview/actions/actions.forms/samplers/index.ts b/frontend/webapp/components/overview/actions/actions.forms/samplers/index.ts new file mode 100644 index 000000000..76be58f64 --- /dev/null +++ b/frontend/webapp/components/overview/actions/actions.forms/samplers/index.ts @@ -0,0 +1,2 @@ +export * from './error-sampler'; +export * from './probabilistic-sampler'; diff --git a/frontend/webapp/components/overview/actions/actions.forms/samplers/probabilistic-sampler/index.tsx b/frontend/webapp/components/overview/actions/actions.forms/samplers/probabilistic-sampler/index.tsx new file mode 100644 index 000000000..0a11a050a --- /dev/null +++ b/frontend/webapp/components/overview/actions/actions.forms/samplers/probabilistic-sampler/index.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import styled from 'styled-components'; +import { KeyvalInput } from '@/design.system'; + +const FormWrapper = styled.div` + width: 375px; +`; + +interface ProbabilisticSampler { + sampling_percentage: string; +} + +interface ProbabilisticSamplerProps { + data: ProbabilisticSampler; + onChange: (key: string, value: ProbabilisticSampler | null) => void; +} +const ACTION_DATA_KEY = 'actionData'; +export function ProbabilisticSamplerForm({ + data, + onChange, +}: ProbabilisticSamplerProps): React.JSX.Element { + console.log({ data }); + + function handleOnChange(sampling_percentage: string): void { + onChange(ACTION_DATA_KEY, { + sampling_percentage, + }); + } + + return ( + <> + + handleOnChange(value)} + type="number" + tooltip="Percentage at which items are sampled; = 100 samples all items, 0 rejects all items" + min={0} + max={100} + error={ + +data?.sampling_percentage > 100 + ? 'Value must be less than 100' + : '' + } + /> + + + ); +} diff --git a/frontend/webapp/components/overview/actions/actions.table/action.row.dynamic.content/index.tsx b/frontend/webapp/components/overview/actions/actions.table/action.row.dynamic.content/index.tsx index 9e1c281a5..438fc19b6 100644 --- a/frontend/webapp/components/overview/actions/actions.table/action.row.dynamic.content/index.tsx +++ b/frontend/webapp/components/overview/actions/actions.table/action.row.dynamic.content/index.tsx @@ -30,6 +30,18 @@ export default function ActionRowDynamicContent({ {`${Object.keys(item?.spec?.renames).length} renamed attributes`} ); + case ActionsType.ERROR_SAMPLER: + return ( + + {`${item?.spec?.fallback_sampling_ratio}% sampling ratio`}s + + ); + case ActionsType.PROBABILISTIC_SAMPLER: + return ( + + {`${item?.spec?.sampling_percentage}% sampling ratio`} + + ); default: return
{item.type}
; } diff --git a/frontend/webapp/components/overview/actions/actions.table/actions.table.row.tsx b/frontend/webapp/components/overview/actions/actions.table/actions.table.row.tsx index 08b7c16af..aba2ae5ff 100644 --- a/frontend/webapp/components/overview/actions/actions.table/actions.table.row.tsx +++ b/frontend/webapp/components/overview/actions/actions.table/actions.table.row.tsx @@ -33,9 +33,9 @@ const StyledMainTd = styled(StyledTd)` const ActionIconContainer = styled.div` display: flex; - align-items: center; gap: 8px; margin-left: 10px; + width: 100%; `; const ActionDetails = styled.div` @@ -114,16 +114,13 @@ export function ActionsTableRow({ return ( - onRowClick(item.id)} - isFirstRow={index === 0} - > + onSelectedCheckboxChange(item.id)} /> - -
+ onRowClick(item.id)}> +
diff --git a/frontend/webapp/components/overview/actions/delete.action/index.tsx b/frontend/webapp/components/overview/actions/delete.action/index.tsx index 4efaf046f..f9de26562 100644 --- a/frontend/webapp/components/overview/actions/delete.action/index.tsx +++ b/frontend/webapp/components/overview/actions/delete.action/index.tsx @@ -59,7 +59,7 @@ export function DeleteAction({
- {`${OVERVIEW.DELETE} ${name}`} + {`${OVERVIEW.DELETE} ${name} Action`} )} diff --git a/frontend/webapp/components/overview/actions/new.action.card/index.tsx b/frontend/webapp/components/overview/actions/new.action.card/index.tsx index ae6f02551..8a64c670d 100644 --- a/frontend/webapp/components/overview/actions/new.action.card/index.tsx +++ b/frontend/webapp/components/overview/actions/new.action.card/index.tsx @@ -28,7 +28,7 @@ export function NewActionCard({ item, onClick }: NewActionCardProps) { return ( onClick({ item })}> - + {item.title} diff --git a/frontend/webapp/containers/main/actions/choose-action/index.tsx b/frontend/webapp/containers/main/actions/choose-action/index.tsx index 93f7ea77f..0554ead48 100644 --- a/frontend/webapp/containers/main/actions/choose-action/index.tsx +++ b/frontend/webapp/containers/main/actions/choose-action/index.tsx @@ -33,6 +33,20 @@ const ITEMS = [ type: ActionsType.RENAME_ATTRIBUTES, icon: ActionsType.RENAME_ATTRIBUTES, }, + { + id: 'error-sampler', + title: 'Error Sampler', + description: 'Sample errors based on percentage.', + type: ActionsType.ERROR_SAMPLER, + icon: ActionsType.ERROR_SAMPLER, + }, + { + id: 'probabilistic-sampler', + title: 'Probabilistic Sampler', + description: 'Sample traces based on percentage.', + type: ActionsType.PROBABILISTIC_SAMPLER, + icon: ActionsType.PROBABILISTIC_SAMPLER, + }, ]; export function ChooseActionContainer(): React.JSX.Element { diff --git a/frontend/webapp/containers/main/actions/create-action/index.tsx b/frontend/webapp/containers/main/actions/create-action/index.tsx index 51d74f5a3..2bb60c977 100644 --- a/frontend/webapp/containers/main/actions/create-action/index.tsx +++ b/frontend/webapp/containers/main/actions/create-action/index.tsx @@ -31,7 +31,12 @@ import { const ACTION_TYPE = 'type'; export function CreateActionContainer(): React.JSX.Element { - const { actionState, onChangeActionState, upsertAction } = useActionState(); + const { + actionState, + onChangeActionState, + upsertAction, + getSupportedSignals, + } = useActionState(); const { actionName, actionNote, actionData, selectedMonitors, type } = actionState; @@ -73,7 +78,7 @@ export function CreateActionContainer(): React.JSX.Element { onChangeActionState('selectedMonitors', newMonitors) } @@ -98,9 +103,6 @@ export function CreateActionContainer(): React.JSX.Element { onChange={(e) => onChangeActionState('actionNote', e.target.value)} /> - {/*
- -
*/} @@ -109,9 +111,6 @@ export function CreateActionContainer(): React.JSX.Element { - {/* - - */} ); } diff --git a/frontend/webapp/containers/main/actions/edit-action/index.tsx b/frontend/webapp/containers/main/actions/edit-action/index.tsx index 299734378..b3c3150bf 100644 --- a/frontend/webapp/containers/main/actions/edit-action/index.tsx +++ b/frontend/webapp/containers/main/actions/edit-action/index.tsx @@ -1,7 +1,7 @@ 'use client'; import React, { useEffect } from 'react'; import theme from '@/styles/palette'; -import { useActionState } from '@/hooks'; +import { Monitor, useActionState } from '@/hooks'; import { useSearchParams } from 'next/navigation'; import { ACTION, ACTIONS, ACTION_ITEM_DOCS_LINK } from '@/utils'; import { @@ -40,6 +40,7 @@ export function EditActionContainer(): React.JSX.Element { upsertAction, buildActionData, onDeleteAction, + getSupportedSignals, } = useActionState(); const { @@ -100,7 +101,7 @@ export function EditActionContainer(): React.JSX.Element { onChangeActionState('selectedMonitors', newMonitors) } diff --git a/frontend/webapp/design.system/input/input.tsx b/frontend/webapp/design.system/input/input.tsx index 580a55fe6..b528a3d77 100644 --- a/frontend/webapp/design.system/input/input.tsx +++ b/frontend/webapp/design.system/input/input.tsx @@ -8,6 +8,10 @@ interface InputProps { error?: string; style?: React.CSSProperties; required?: boolean; + placeholder?: string; + tooltip?: string; + min?: number; + max?: number; } export function KeyvalInput(props: InputProps): JSX.Element { diff --git a/frontend/webapp/hooks/actions/useActionState.ts b/frontend/webapp/hooks/actions/useActionState.ts index 885db0272..ecb31212b 100644 --- a/frontend/webapp/hooks/actions/useActionState.ts +++ b/frontend/webapp/hooks/actions/useActionState.ts @@ -6,7 +6,7 @@ import { useRouter } from 'next/navigation'; import { putAction, setAction, deleteAction } from '@/services'; import { ActionData, ActionItem, ActionState, ActionsType } from '@/types'; -interface Monitor { +export interface Monitor { id: string; label: string; checked: boolean; @@ -84,7 +84,7 @@ export function useActionState() { type, } = actionState; - const signals = selectedMonitors + const signals = getSupportedSignals(type, selectedMonitors) .filter((monitor) => monitor.checked) .map((monitor) => monitor.label.toUpperCase()); @@ -123,12 +123,24 @@ export function useActionState() { } catch (error) {} } + function getSupportedSignals(type: string, signals: Monitor[]) { + if ( + type === ActionsType.ERROR_SAMPLER || + type === ActionsType.PROBABILISTIC_SAMPLER + ) { + return signals.filter((signal) => signal.label === 'Traces'); + } + + return signals; + } + return { actionState, - onChangeActionState, upsertAction, - buildActionData, onDeleteAction, + buildActionData, + getSupportedSignals, + onChangeActionState, }; } @@ -179,6 +191,14 @@ function getActionDataByType(action: ActionData | undefined) { return { renames: action.spec.renames, }; + case ActionsType.ERROR_SAMPLER: + return { + fallback_sampling_ratio: action.spec.fallback_sampling_ratio, + }; + case ActionsType.PROBABILISTIC_SAMPLER: + return { + sampling_percentage: action.spec.sampling_percentage, + }; default: return {}; } diff --git a/frontend/webapp/package.json b/frontend/webapp/package.json index 0626c5acc..3d3733390 100644 --- a/frontend/webapp/package.json +++ b/frontend/webapp/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@focus-reactive/react-yaml": "^1.1.2", - "@keyval-dev/design-system": "^1.9.7", + "@keyval-dev/design-system": "^2.0.2", "@next/font": "^13.4.7", "@reduxjs/toolkit": "^2.2.1", "@svgr/webpack": "^6.2.1", diff --git a/frontend/webapp/types/actions.ts b/frontend/webapp/types/actions.ts index f5d18f6eb..d33ce75eb 100644 --- a/frontend/webapp/types/actions.ts +++ b/frontend/webapp/types/actions.ts @@ -2,6 +2,8 @@ export enum ActionsType { ADD_CLUSTER_INFO = 'AddClusterInfo', DELETE_ATTRIBUTES = 'DeleteAttribute', RENAME_ATTRIBUTES = 'RenameAttribute', + ERROR_SAMPLER = 'ErrorSampler', + PROBABILISTIC_SAMPLER = 'ProbabilisticSampler', } export enum ActionsSortType { diff --git a/frontend/webapp/utils/constants/string.tsx b/frontend/webapp/utils/constants/string.tsx index 457695dd7..0046a0853 100644 --- a/frontend/webapp/utils/constants/string.tsx +++ b/frontend/webapp/utils/constants/string.tsx @@ -154,6 +154,14 @@ export const ACTIONS = { TITLE: 'Rename Attribute', DESCRIPTION: `The “Rename Attribute” Odigos Action can be used to rename attributes from telemetry signals originated from the k8s cluster where the Odigos is running.`, }, + ErrorSampler: { + TITLE: 'Error Sampler', + DESCRIPTION: `The “Error Sampler” Odigos Action is a Global Action that supports error sampling by filtering out non-error traces.`, + }, + ProbabilisticSampler: { + TITLE: 'Probabilistic Sampler', + DESCRIPTION: `The “Probabilistic Sampler” Odigos Action is a Global Action that supports probabilistic sampling by filtering out traces based on a sampling ratio.`, + }, SEARCH_ACTION: 'Search Action', }; diff --git a/frontend/webapp/yarn.lock b/frontend/webapp/yarn.lock index bc869a813..fa1c7f596 100644 --- a/frontend/webapp/yarn.lock +++ b/frontend/webapp/yarn.lock @@ -1450,10 +1450,10 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@keyval-dev/design-system@^1.9.7": - version "1.9.7" - resolved "https://registry.yarnpkg.com/@keyval-dev/design-system/-/design-system-1.9.7.tgz#4aeef309f8e6d692268dd20d392d3c15fe06a245" - integrity sha512-T4TbKJTkMLTjb0aXqBwSP7idLXhY/kFKfAkExsmdgFgAP+qjuXIfnK0bmxp41ZV6WbKcLvgnQ1E5/CrPJR1YUQ== +"@keyval-dev/design-system@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@keyval-dev/design-system/-/design-system-2.0.2.tgz#6d1866af368bcfd3a1d07f66287b452ccd885f40" + integrity sha512-14+ga4fVgyo1kRTODQeRgxkZGF7Egyn/CgZcqCqq8tKKniXvimUu+RDRjDT6rzwHkktAX7E50KiT+E8bVtT3QQ== dependencies: "@focus-reactive/react-yaml" "^1.1.2" "@svgr/core" "^8.0.0"