From 969efca407f14e0fc256d69fef3d7def705157e6 Mon Sep 17 00:00:00 2001 From: Matheus Nogueira Date: Wed, 24 Jan 2024 18:08:10 -0300 Subject: [PATCH 1/7] feat: unsubscribe from subscription (#3557) * feat: unsubscribe from subscription * fix: resolve TODO item in connection testing --- server/executor/queue.go | 2 +- server/executor/test_suite_runner_test.go | 4 +- server/http/websocket/unsubscribe.go | 6 ++- server/subscription/in_memory_manager.go | 28 ++++++++++--- server/subscription/in_memory_manager_test.go | 4 +- server/subscription/manager.go | 41 +++++++++++++++++-- server/subscription/nats_manager.go | 15 ++++--- server/subscription/subscription.go | 7 ---- server/testconnection/otlp.go | 5 +-- 9 files changed, 81 insertions(+), 31 deletions(-) delete mode 100644 server/subscription/subscription.go diff --git a/server/executor/queue.go b/server/executor/queue.go index fe3e354725..7fb7dc0146 100644 --- a/server/executor/queue.go +++ b/server/executor/queue.go @@ -179,7 +179,7 @@ type testSuiteRunGetter interface { } type subscriptor interface { - Subscribe(string, subscription.Subscriber) + Subscribe(string, subscription.Subscriber) subscription.Subscription } type queueConfigurer[T any] struct { diff --git a/server/executor/test_suite_runner_test.go b/server/executor/test_suite_runner_test.go index 7f45c2d7b5..fb9b3c24ce 100644 --- a/server/executor/test_suite_runner_test.go +++ b/server/executor/test_suite_runner_test.go @@ -192,11 +192,11 @@ func runTestSuiteRunnerTest(t *testing.T, withErrors bool, assert func(t *testin return nil }) - subscriptionManager.Subscribe(transactionRun.ResourceID(), sf) + subscription := subscriptionManager.Subscribe(transactionRun.ResourceID(), sf) select { case finalRun := <-done: - subscriptionManager.Unsubscribe(transactionRun.ResourceID(), sf.ID()) //cleanup to avoid race conditions + subscription.Unsubscribe() assert(t, finalRun) case <-time.After(10 * time.Second): t.Log("timeout after 10 second") diff --git a/server/http/websocket/unsubscribe.go b/server/http/websocket/unsubscribe.go index 430c609e67..151a7b1d60 100644 --- a/server/http/websocket/unsubscribe.go +++ b/server/http/websocket/unsubscribe.go @@ -36,7 +36,11 @@ func (e unsubscribeCommandExecutor) Execute(conn *websocket.Conn, message []byte return } - e.subscriptionManager.Unsubscribe(msg.Resource, msg.SubscriptionId) + subscription := e.subscriptionManager.GetSubscription(msg.Resource, msg.SubscriptionId) + err = subscription.Unsubscribe() + if err != nil { + conn.WriteJSON(ErrorMessage(fmt.Errorf("could not unsubscribe: %w", err))) + } conn.WriteJSON(UnsubscribeSuccess()) } diff --git a/server/subscription/in_memory_manager.go b/server/subscription/in_memory_manager.go index d29b30f464..f935a4b358 100644 --- a/server/subscription/in_memory_manager.go +++ b/server/subscription/in_memory_manager.go @@ -6,28 +6,46 @@ import ( ) type inMemoryManager struct { - subscriptions map[string][]Subscriber + subscribers map[string][]Subscriber + subscriptions *subscriptionStorage mutex sync.Mutex } +type inMemorySubscription struct { + unsubscribeFn func() +} + +func (s *inMemorySubscription) Unsubscribe() error { + s.unsubscribeFn() + return nil +} + func (m *inMemoryManager) getSubscribers(resourceID string) []Subscriber { m.mutex.Lock() defer m.mutex.Unlock() - return m.subscriptions[resourceID] + return m.subscribers[resourceID] } func (m *inMemoryManager) setSubscribers(resourceID string, subscribers []Subscriber) { m.mutex.Lock() defer m.mutex.Unlock() - m.subscriptions[resourceID] = subscribers + m.subscribers[resourceID] = subscribers } -func (m *inMemoryManager) Subscribe(resourceID string, subscriber Subscriber) { +func (m *inMemoryManager) Subscribe(resourceID string, subscriber Subscriber) Subscription { subscribers := append(m.getSubscribers(resourceID), subscriber) m.setSubscribers(resourceID, subscribers) + + return &inMemorySubscription{ + unsubscribeFn: func() { m.unsubscribe(resourceID, subscriber.ID()) }, + } +} + +func (m *inMemoryManager) GetSubscription(resourceID string, subscriptionID string) Subscription { + return m.subscriptions.Get(resourceID, subscriptionID) } -func (m *inMemoryManager) Unsubscribe(resourceID string, subscriptionID string) { +func (m *inMemoryManager) unsubscribe(resourceID string, subscriptionID string) { subscribers := m.getSubscribers(resourceID) updated := make([]Subscriber, 0, len(subscribers)-1) diff --git a/server/subscription/in_memory_manager_test.go b/server/subscription/in_memory_manager_test.go index bb9fbd2196..2d3d063025 100644 --- a/server/subscription/in_memory_manager_test.go +++ b/server/subscription/in_memory_manager_test.go @@ -101,13 +101,13 @@ func TestManagerUnsubscribe(t *testing.T) { Content: "Test was deleted", } - manager.Subscribe("test:1", subscriber) + subscription := manager.Subscribe("test:1", subscriber) manager.PublishUpdate(message1) assert.Equal(t, message1.Type, receivedMessage.Type) assert.Equal(t, message1.Content, receivedMessage.Content) - manager.Unsubscribe("test:1", subscriber.ID()) + subscription.Unsubscribe() manager.PublishUpdate(message2) assert.Equal(t, message1.Type, receivedMessage.Type, "subscriber should not be notified after unsubscribed") diff --git a/server/subscription/manager.go b/server/subscription/manager.go index ac24c75408..5e3f68b043 100644 --- a/server/subscription/manager.go +++ b/server/subscription/manager.go @@ -1,19 +1,24 @@ package subscription import ( + "fmt" "sync" "github.com/nats-io/nats.go" ) type Manager interface { - Subscribe(resourceID string, subscriber Subscriber) - Unsubscribe(resourceID string, subscriptionID string) + Subscribe(resourceID string, subscriber Subscriber) Subscription + GetSubscription(resourceID string, subscriptionID string) Subscription PublishUpdate(message Message) Publish(resourceID string, message any) } +type Subscription interface { + Unsubscribe() error +} + type optFn func(*options) type options struct { @@ -34,11 +39,39 @@ func NewManager(opts ...optFn) Manager { } if currentOptions.conn != nil { - return &natsManager{currentOptions.conn} + return &natsManager{ + currentOptions.conn, + newSubscriptionStorage(), + } } return &inMemoryManager{ - subscriptions: make(map[string][]Subscriber), + subscribers: make(map[string][]Subscriber), + subscriptions: newSubscriptionStorage(), mutex: sync.Mutex{}, } } + +type subscriptionStorage struct { + subscriptions map[string]Subscription +} + +func newSubscriptionStorage() *subscriptionStorage { + return &subscriptionStorage{ + subscriptions: make(map[string]Subscription), + } +} + +func (s *subscriptionStorage) Get(resourceID, subscriberID string) Subscription { + key := s.key(resourceID, subscriberID) + return s.subscriptions[key] +} + +func (s *subscriptionStorage) Set(resourceID, subscriberID string, subscription Subscription) { + key := s.key(resourceID, subscriberID) + s.subscriptions[key] = subscription +} + +func (s *subscriptionStorage) key(resourceID, subscriberID string) string { + return fmt.Sprintf("%s-%s", resourceID, subscriberID) +} diff --git a/server/subscription/nats_manager.go b/server/subscription/nats_manager.go index c41c6da814..b0cf7385cb 100644 --- a/server/subscription/nats_manager.go +++ b/server/subscription/nats_manager.go @@ -7,11 +7,12 @@ import ( ) type natsManager struct { - conn *nats.Conn + conn *nats.Conn + subscriptions *subscriptionStorage } -func (m *natsManager) Subscribe(resourceID string, subscriber Subscriber) { - _, err := m.conn.Subscribe(resourceID, func(msg *nats.Msg) { +func (m *natsManager) Subscribe(resourceID string, subscriber Subscriber) Subscription { + subscription, err := m.conn.Subscribe(resourceID, func(msg *nats.Msg) { decoded, err := DecodeMessage(msg.Data) if err != nil { log.Printf("cannot unmarshall incoming nats message: %s", err.Error()) @@ -25,12 +26,14 @@ func (m *natsManager) Subscribe(resourceID string, subscriber Subscriber) { }) if err != nil { log.Printf("cannot subscribe to nats topic: %s", err.Error()) - return + return nil } + + return subscription } -func (m *natsManager) Unsubscribe(resourceID string, subscriptionID string) { - panic("nats unsubscribe not implemented") +func (m *natsManager) GetSubscription(resourceID string, subscriptionID string) Subscription { + return m.subscriptions.Get(resourceID, subscriptionID) } func (m *natsManager) PublishUpdate(message Message) { diff --git a/server/subscription/subscription.go b/server/subscription/subscription.go deleted file mode 100644 index e2279e5c04..0000000000 --- a/server/subscription/subscription.go +++ /dev/null @@ -1,7 +0,0 @@ -package subscription - -// Subscription represents a group of subscribers that are waiting for updates of a specific resource -type Subscription struct { - resourceID string - subscribers []Subscriber -} diff --git a/server/testconnection/otlp.go b/server/testconnection/otlp.go index 5a95b8dd7b..6595694d2a 100644 --- a/server/testconnection/otlp.go +++ b/server/testconnection/otlp.go @@ -105,9 +105,8 @@ func (t *OTLPConnectionTester) GetSpanCount(ctx context.Context, opts ...GetSpan return nil }) - t.subscriptionManager.Subscribe(topicName, subscriber) - // TODO: implement subscription - // defer t.subscriptionManager.Unsubscribe(topicName, subscriber.ID()) + subscription := t.subscriptionManager.Subscribe(topicName, subscriber) + defer subscription.Unsubscribe() t.subscriptionManager.Publish(GetSpanCountTopicName(WithTenantID(tenantID)), OTLPConnectionTestRequest{}) From 01c297418ed7b13405964f81d76800fd9b9061c9 Mon Sep 17 00:00:00 2001 From: Jorge Padilla Date: Wed, 24 Jan 2024 17:28:39 -0500 Subject: [PATCH 2/7] feat(frontend): add help info in settings pages (#3558) --- .../Settings/DataStore/DataStore.styled.ts | 4 ++- web/src/components/Settings/Demo/Demo.tsx | 32 ++++++++++++------- web/src/components/Settings/Linter/Linter.tsx | 5 +-- .../components/Settings/Polling/Polling.tsx | 12 +++++-- .../Settings/TestRunner/TestRunner.tsx | 7 ++-- .../Settings/common/Settings.styled.ts | 12 +++++-- web/src/constants/Common.constants.ts | 5 ++- .../TracetestAttributes.constants.ts | 2 +- 8 files changed, 56 insertions(+), 23 deletions(-) diff --git a/web/src/components/Settings/DataStore/DataStore.styled.ts b/web/src/components/Settings/DataStore/DataStore.styled.ts index 9e600196ab..3faa39ef72 100644 --- a/web/src/components/Settings/DataStore/DataStore.styled.ts +++ b/web/src/components/Settings/DataStore/DataStore.styled.ts @@ -10,9 +10,11 @@ export const Title = styled(Typography.Title)` `; export const Wrapper = styled.div` + background: ${({theme}) => theme.color.white}; + border-radius: 2px; + border: ${({theme}) => `1px solid ${theme.color.borderLight}`}; display: flex; flex-direction: column; - background: ${({theme}) => theme.color.white}; `; export const FormContainer = styled.div` diff --git a/web/src/components/Settings/Demo/Demo.tsx b/web/src/components/Settings/Demo/Demo.tsx index 0fc82a927c..07395a9dc9 100644 --- a/web/src/components/Settings/Demo/Demo.tsx +++ b/web/src/components/Settings/Demo/Demo.tsx @@ -1,20 +1,30 @@ -import {OTEL_DEMO_GITHUB, POKESHOP_GITHUB} from 'constants/Common.constants'; +import DocsBanner from 'components/DocsBanner'; +import {DEMO_DOCUMENTATION_URL, OTEL_DEMO_GITHUB, POKESHOP_GITHUB} from 'constants/Common.constants'; import DemoForm from './DemoForm'; import * as S from '../common/Settings.styled'; const Demo = () => ( + Demo - Tracetest has the option to enable Test examples for our{' '} - - Pokeshop Demo App - {' '} - or the{' '} - - OpenTelemetry Astronomy Shop Demo - - . You will require an instance of those applications running alongside your Tracetest server to be able to use - them. You can adjust the demo values below: +

+ Tracetest has the option to enable Test examples for our{' '} + + Pokeshop Demo App + {' '} + or the{' '} + + OpenTelemetry Astronomy Shop Demo + + . You will require an instance of those applications running alongside your Tracetest server to be able to use + them. +

+ + Need more information about the Demos?{' '} + + Learn more in our docs + +
diff --git a/web/src/components/Settings/Linter/Linter.tsx b/web/src/components/Settings/Linter/Linter.tsx index 19980467f8..c577e28a39 100644 --- a/web/src/components/Settings/Linter/Linter.tsx +++ b/web/src/components/Settings/Linter/Linter.tsx @@ -5,6 +5,7 @@ import * as S from '../common/Settings.styled'; const Linter = () => ( + Analyzer (Beta)

The Tracetest Analyzer is a plugin based framework used to audit OpenTelemetry traces to help teams improve @@ -13,9 +14,9 @@ const Linter = () => ( Add to this Issue or call us on Slack!

- Need more information about Analyzer?{' '} + Need more information about the Analyzer?{' '} - Go to our docs + Learn more in our docs
diff --git a/web/src/components/Settings/Polling/Polling.tsx b/web/src/components/Settings/Polling/Polling.tsx index de18dcfd63..3f538310d4 100644 --- a/web/src/components/Settings/Polling/Polling.tsx +++ b/web/src/components/Settings/Polling/Polling.tsx @@ -1,11 +1,19 @@ +import DocsBanner from 'components/DocsBanner'; +import {TRACE_POLLING_DOCUMENTATION_URL} from 'constants/Common.constants'; import PollingForm from './PollingForm'; import * as S from '../common/Settings.styled'; const Polling = () => ( + Trace Polling - Tracetest uses polling to gather the distributed trace associated with each test run. You can adjust the polling - values below: +

Tracetest uses polling to gather the distributed trace associated with each test run.

+ + Need more information about Trace Polling?{' '} + + Learn more in our docs + +
diff --git a/web/src/components/Settings/TestRunner/TestRunner.tsx b/web/src/components/Settings/TestRunner/TestRunner.tsx index e3b96ffdd5..5b4c7da055 100644 --- a/web/src/components/Settings/TestRunner/TestRunner.tsx +++ b/web/src/components/Settings/TestRunner/TestRunner.tsx @@ -6,12 +6,13 @@ import TestRunnerForm from './TestRunnerForm'; const TestRunner = () => { return ( + Test Runner -

The Test Runner allows you to configure the behavior used to execute your tests and generate the results

+

The Test Runner allows you to configure the behavior used to execute your tests and generate the results.

- Need more information about Test Runner?{' '} + Need more information about the Test Runner?{' '} - Go to our docs + Learn more in our docs
diff --git a/web/src/components/Settings/common/Settings.styled.ts b/web/src/components/Settings/common/Settings.styled.ts index 11369155b3..1dbf304162 100644 --- a/web/src/components/Settings/common/Settings.styled.ts +++ b/web/src/components/Settings/common/Settings.styled.ts @@ -3,10 +3,12 @@ import styled from 'styled-components'; export const Container = styled.div` background: ${({theme}) => theme.color.white}; + border: ${({theme}) => `1px solid ${theme.color.borderLight}`}; + border-radius: 2px; display: flex; flex-direction: column; margin-bottom: 24px; - padding: 16px; + padding: 24px; `; export const FormContainer = styled.div` @@ -21,7 +23,7 @@ export const FooterContainer = styled.div` border-top: 1px solid ${({theme}) => theme.color.borderLight}; `; -export const Description = styled(Typography.Text)` +export const Description = styled(Typography.Paragraph)` && { color: ${({theme}) => theme.color.textSecondary}; } @@ -50,3 +52,9 @@ export const SwitchLabel = styled.label<{$disabled?: boolean}>` cursor: ${({$disabled}) => ($disabled ? 'not-allowed' : 'pointer')}; margin-bottom: 24px; `; + +export const Title = styled(Typography.Title)` + && { + margin-bottom: 16px; + } +`; diff --git a/web/src/constants/Common.constants.ts b/web/src/constants/Common.constants.ts index 7b29bb61d7..2ed902622c 100644 --- a/web/src/constants/Common.constants.ts +++ b/web/src/constants/Common.constants.ts @@ -30,8 +30,11 @@ export const TEST_RUNNER_DOCUMENTATION_URL = 'https://docs.tracetest.io/configur export const ANALYZER_RULES_DOCUMENTATION_URL = 'https://docs.tracetest.io/analyzer/rules'; export const EXPRESSIONS_DOCUMENTATION_URL = 'https://docs.tracetest.io/concepts/expressions'; export const VARIABLE_SET_DOCUMENTATION_URL = 'https://docs.tracetest.io/concepts/variable-sets'; -export const GITHUB_ACTION_URL = 'https://github.com/kubeshop/tracetest-github-action/tree/main/examples/tracetest-cli-with-tracetest-core'; +export const GITHUB_ACTION_URL = + 'https://github.com/kubeshop/tracetest-github-action/tree/main/examples/tracetest-cli-with-tracetest-core'; export const CLI_DOCS_URL = 'https://docs.tracetest.io/cli/cli-installation-reference'; +export const TRACE_POLLING_DOCUMENTATION_URL = 'https://docs.tracetest.io/configuration/trace-polling/'; +export const DEMO_DOCUMENTATION_URL = 'https://docs.tracetest.io/configuration/demo/'; export const SELECTOR_LANGUAGE_CHEAT_SHEET_URL = `${process.env.PUBLIC_URL}/SL_cheat_sheet.pdf`; diff --git a/web/src/constants/TracetestAttributes.constants.ts b/web/src/constants/TracetestAttributes.constants.ts index 1d11176fd3..5ba333603c 100644 --- a/web/src/constants/TracetestAttributes.constants.ts +++ b/web/src/constants/TracetestAttributes.constants.ts @@ -16,7 +16,7 @@ export default { }, 'tracetest.span.type': { description: - 'Tracetest attribute based on the [OTel Trace Semantic Conventions](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions)', + 'Tracetest attribute based on the [OTel Trace Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/README.md)', note: '', tags: ['general', 'http', 'database', 'rpc', 'messaging', 'faas', 'exception'], }, From 2b7baf7fa91733142808091b1e423e5616ed00e4 Mon Sep 17 00:00:00 2001 From: Jorge Padilla Date: Thu, 25 Jan 2024 10:09:38 -0500 Subject: [PATCH 3/7] fix(frontend): fix overflow issue in fields (#3560) --- .../components/Fields/Auth/AuthApiKeyBase.tsx | 4 ++-- web/src/components/Fields/Auth/AuthBasic.tsx | 4 ++-- .../Fields/KeyValueList/KeyValueList.styled.ts | 5 +++++ .../Fields/KeyValueList/KeyValueList.tsx | 16 ++++++++++------ .../Fields/Metadata/Metadata.styled.ts | 5 +++++ web/src/components/Fields/Metadata/Metadata.tsx | 16 ++++++++++------ web/src/components/Fields/PlainAuth/Fields.tsx | 4 ++-- 7 files changed, 36 insertions(+), 18 deletions(-) diff --git a/web/src/components/Fields/Auth/AuthApiKeyBase.tsx b/web/src/components/Fields/Auth/AuthApiKeyBase.tsx index 2e48be0dbd..feb938f702 100644 --- a/web/src/components/Fields/Auth/AuthApiKeyBase.tsx +++ b/web/src/components/Fields/Auth/AuthApiKeyBase.tsx @@ -12,7 +12,7 @@ const AuthApiKeyBase = ({baseName}: IProps) => ( ( ( ( theme.color.textSecondary}; font-size: ${({theme}) => theme.size.md}; `; + +export const Item = styled.div` + flex: 1; + overflow: hidden; +`; diff --git a/web/src/components/Fields/KeyValueList/KeyValueList.tsx b/web/src/components/Fields/KeyValueList/KeyValueList.tsx index 6c93dc88e5..4d0efbf6f4 100644 --- a/web/src/components/Fields/KeyValueList/KeyValueList.tsx +++ b/web/src/components/Fields/KeyValueList/KeyValueList.tsx @@ -29,13 +29,17 @@ const KeyValueList = ({ <> {fields.map((field, index) => ( - - - + + + + + - - - + + + + +