From 9bf77d4035cd6869db57b0094f293d10d979166a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20D=C4=85browski?= Date: Tue, 17 Feb 2026 11:59:01 +0100 Subject: [PATCH] Add logs reference in the hermes-console --- hermes-console/json-server/db.json | 6 +++ hermes-console/src/api/app-configuration.ts | 8 +++ hermes-console/src/dummy/app-config.ts | 8 +++ hermes-console/src/i18n/en-US/index.ts | 8 +++ .../subscription/SubscriptionView.spec.ts | 46 +++++++++++++++++ .../views/subscription/SubscriptionView.vue | 49 +++++++++++++++++++ .../src/views/topic/TopicView.spec.ts | 46 +++++++++++++++++ hermes-console/src/views/topic/TopicView.vue | 47 ++++++++++++++++++ .../config/console/ConsoleProperties.java | 48 ++++++++++++++++++ 9 files changed, 266 insertions(+) diff --git a/hermes-console/json-server/db.json b/hermes-console/json-server/db.json index 5f39b8d4c0..b8f3d5446c 100644 --- a/hermes-console/json-server/db.json +++ b/hermes-console/json-server/db.json @@ -753,6 +753,12 @@ "topicDetailsUrl": "", "subscriptionIframeUrl": "", "subscriptionDetailsUrl": "" + }, + "logs": { + "enabled": true, + "baseUrl": "https://logging.example.com/app/discover", + "topicLogsFilter": "#/?_a=(filters:!((query:(match_phrase:(topic-name:'{{topic_name}}')))))", + "subscriptionLogsFilter": "#/?_a=(filters:!((query:(match_phrase:(subscription-name:'{{subscription_name}}')))))" } }, "stats": { diff --git a/hermes-console/src/api/app-configuration.ts b/hermes-console/src/api/app-configuration.ts index 603865e02c..a46340d7dd 100644 --- a/hermes-console/src/api/app-configuration.ts +++ b/hermes-console/src/api/app-configuration.ts @@ -10,6 +10,7 @@ export interface AppConfiguration { consistency: ConsistencyViewConfiguration; group: GroupViewConfiguration; costs: CostsConfiguration; + logs: LogsConfiguration; } export interface ConsoleConfiguration { @@ -176,3 +177,10 @@ export interface OfflineRetransmission { globalTaskQueueUrl: string; monitoringDocsUrl: string; } + +export interface LogsConfiguration { + enabled: boolean; + baseUrl: string; + topicLogsFilter: string; + subscriptionLogsFilter: string; +} diff --git a/hermes-console/src/dummy/app-config.ts b/hermes-console/src/dummy/app-config.ts index 98e736072e..6b6c0cd565 100644 --- a/hermes-console/src/dummy/app-config.ts +++ b/hermes-console/src/dummy/app-config.ts @@ -137,4 +137,12 @@ export const dummyAppConfig: AppConfiguration = { subscriptionIframeUrl: '', subscriptionDetailsUrl: '', }, + logs: { + enabled: true, + baseUrl: 'https://logging.example.com/app/discover', + topicLogsFilter: + "#/?_a=(filters:!((query:(match_phrase:(topic-name:'{{topic_name}}')))))", + subscriptionLogsFilter: + "#/?_a=(filters:!((query:(match_phrase:(subscription-name:'{{subscription_name}}')))))", + }, }; diff --git a/hermes-console/src/i18n/en-US/index.ts b/hermes-console/src/i18n/en-US/index.ts index 382d42e5ab..49d62ddc16 100644 --- a/hermes-console/src/i18n/en-US/index.ts +++ b/hermes-console/src/i18n/en-US/index.ts @@ -315,6 +315,7 @@ const en_US = { offlineClients: 'Offline clients', messages: 'Messages', offlineRetransmission: 'Offline retransmission', + logs: 'Logs', }, confirmationDialog: { remove: { @@ -443,6 +444,7 @@ const en_US = { messages: 'Messages', filters: 'Filters', mutations: 'Mutations', + logs: 'Logs', }, confirmationDialog: { remove: { @@ -911,6 +913,12 @@ const en_US = { title: 'Costs', detailsButton: 'Dashboard', }, + logsCard: { + title: 'Logs', + viewLogs: 'View logs', + description: + 'View Hermes logs for this resource in an external logging provider.', + }, trackingCard: { title: 'Tracking', noTrackingUrls: 'No tracking urls available', diff --git a/hermes-console/src/views/subscription/SubscriptionView.spec.ts b/hermes-console/src/views/subscription/SubscriptionView.spec.ts index ed9da8310d..5b32bbcbf1 100644 --- a/hermes-console/src/views/subscription/SubscriptionView.spec.ts +++ b/hermes-console/src/views/subscription/SubscriptionView.spec.ts @@ -101,6 +101,7 @@ describe('SubscriptionView', () => { 'subscription.tabs.general', 'subscription.tabs.filters', 'subscription.tabs.messages', + 'subscription.tabs.logs', ]; expectedTabs.forEach((boxTitle) => { expect(getByText(boxTitle)).toBeVisible(); @@ -111,6 +112,7 @@ describe('SubscriptionView', () => { 'subscription.tabs.general', 'subscription.tabs.filters', 'subscription.tabs.messages', + 'subscription.tabs.logs', ])('should activate tab on click', async (tab: string) => { // given const user = userEvent.setup(); @@ -433,4 +435,48 @@ describe('SubscriptionView', () => { // then expect(queryByText('trackingCard.title')).not.toBeInTheDocument(); }); + + it('should show logs card on logs tab click', async () => { + // given + const user = userEvent.setup(); + const { getByText } = render(SubscriptionView, { + testPinia: createTestingPiniaWithState(), + }); + + // when + await user.click(getByText('subscription.tabs.logs')); + + // then + expect(getByText('logsCard.title')).toBeVisible(); + expect(getByText('logsCard.viewLogs')).toBeVisible(); + expect(getByText('logsCard.description')).toBeVisible(); + }); + + it('should not show logs tab when kibana is disabled in app config', () => { + // when + const { queryByText } = render(SubscriptionView, { + testPinia: createTestingPinia({ + initialState: { + appConfig: { + appConfig: { + ...dummyAppConfig, + logs: { + enabled: false, + baseUrl: '', + topicLogsFilter: '', + subscriptionLogsFilter: '', + }, + }, + loading: false, + error: { + loadConfig: null, + }, + }, + }, + }), + }); + + // then + expect(queryByText('subscription.tabs.logs')).not.toBeInTheDocument(); + }); }); diff --git a/hermes-console/src/views/subscription/SubscriptionView.vue b/hermes-console/src/views/subscription/SubscriptionView.vue index e8a110624c..069a64b749 100644 --- a/hermes-console/src/views/subscription/SubscriptionView.vue +++ b/hermes-console/src/views/subscription/SubscriptionView.vue @@ -160,11 +160,24 @@ ), })); + const subscriptionLogsUrl = computed(() => { + const logs = configStore.appConfig?.logs; + if (!logs?.baseUrl || !logs?.subscriptionLogsFilter) return ''; + return ( + logs.baseUrl + + logs.subscriptionLogsFilter.replace( + '{{subscription_name}}', + subscriptionId.value, + ) + ); + }); + const Tab = { General: 'general', Messages: 'messages', Filters: 'filters', Mutations: 'mutations', + Logs: 'logs', }; const currentTab = ref(Tab.General); @@ -252,6 +265,12 @@ {{ $t('subscription.tabs.messages') }} + {{ $t('subscription.tabs.logs') }} @@ -350,6 +369,36 @@ + + + + + +
+ + {{ $t('logsCard.title') }} + + + {{ $t('logsCard.viewLogs') }} + +
+
+ + {{ $t('logsCard.description') }} + +
+
+
diff --git a/hermes-console/src/views/topic/TopicView.spec.ts b/hermes-console/src/views/topic/TopicView.spec.ts index e361306a3d..b65b28bca3 100644 --- a/hermes-console/src/views/topic/TopicView.spec.ts +++ b/hermes-console/src/views/topic/TopicView.spec.ts @@ -112,6 +112,7 @@ describe('TopicView', () => { 'topicView.tabs.offlineClients', 'topicView.tabs.messages', 'topicView.tabs.offlineRetransmission', + 'topicView.tabs.logs', ]; // when @@ -132,6 +133,7 @@ describe('TopicView', () => { 'topicView.tabs.offlineClients', 'topicView.tabs.messages', 'topicView.tabs.offlineRetransmission', + 'topicView.tabs.logs', ])('should activate tab on click', async (tab: string) => { // given const user = userEvent.setup(); @@ -455,4 +457,48 @@ describe('TopicView', () => { getByText('topicView.confirmationDialog.remove.text'), ).toBeInTheDocument(); }); + + it('should show logs card on logs tab click', async () => { + // given + const user = userEvent.setup(); + const { getByText } = render(TopicView, { + testPinia: createTestingPiniaWithState(), + }); + + // when + await user.click(getByText('topicView.tabs.logs')); + + // then + expect(getByText('logsCard.title')).toBeVisible(); + expect(getByText('logsCard.viewLogs')).toBeVisible(); + expect(getByText('logsCard.description')).toBeVisible(); + }); + + it('should not show logs tab when kibana is disabled in app config', () => { + // when + const { queryByText } = render(TopicView, { + testPinia: createTestingPinia({ + initialState: { + appConfig: { + appConfig: { + ...dummyAppConfig, + logs: { + enabled: false, + baseUrl: '', + topicLogsFilter: '', + subscriptionLogsFilter: '', + }, + }, + loading: false, + error: { + loadConfig: null, + }, + }, + }, + }), + }); + + // then + expect(queryByText('topicView.tabs.logs')).not.toBeInTheDocument(); + }); }); diff --git a/hermes-console/src/views/topic/TopicView.vue b/hermes-console/src/views/topic/TopicView.vue index 366ea5325f..4f18168e9c 100644 --- a/hermes-console/src/views/topic/TopicView.vue +++ b/hermes-console/src/views/topic/TopicView.vue @@ -124,6 +124,15 @@ topic.value?.offlineStorage.enabled, ); + const topicLogsUrl = computed(() => { + const logs = configStore.appConfig?.logs; + if (!logs?.baseUrl || !logs?.topicLogsFilter) return ''; + return ( + logs.baseUrl + + logs.topicLogsFilter.replace('{{topic_name}}', topicName.value) + ); + }); + const Tab = { General: 'general', Schema: 'schema', @@ -131,6 +140,7 @@ OfflineClients: 'offlineClients', Messages: 'messages', OfflineRetransmission: 'offlineRetransmission', + Logs: 'logs', }; const currentTab = ref(Tab.General); @@ -191,6 +201,13 @@ {{ $t('topicView.tabs.offlineRetransmission') }} + + {{ $t('topicView.tabs.logs') }} + @@ -277,6 +294,36 @@ /> + + + + + +
+ + {{ $t('logsCard.title') }} + + + {{ $t('logsCard.viewLogs') }} + +
+
+ + {{ $t('logsCard.description') }} + +
+
+
diff --git a/hermes-management/src/main/java/pl/allegro/tech/hermes/management/config/console/ConsoleProperties.java b/hermes-management/src/main/java/pl/allegro/tech/hermes/management/config/console/ConsoleProperties.java index 40b9bac0bc..09371e45b7 100644 --- a/hermes-management/src/main/java/pl/allegro/tech/hermes/management/config/console/ConsoleProperties.java +++ b/hermes-management/src/main/java/pl/allegro/tech/hermes/management/config/console/ConsoleProperties.java @@ -26,6 +26,7 @@ public class ConsoleProperties { private ConsistencyView consistency = new ConsistencyView(); private GroupView group = new GroupView(); private Costs costs = new Costs(); + private Logs logs = new Logs(); public Dashboard getDashboard() { return dashboard; @@ -91,6 +92,14 @@ public void setCosts(Costs costs) { this.costs = costs; } + public Logs getLogs() { + return logs; + } + + public void setLogs(Logs logs) { + this.logs = logs; + } + public static final class Console { private String title = "hermes console"; private String contactLink = ""; @@ -954,4 +963,43 @@ public void setMonitoringDocsUrl(String monitoringDocsUrl) { this.monitoringDocsUrl = monitoringDocsUrl; } } + + public static final class Logs { + private boolean enabled = false; + private String baseUrl = ""; + private String topicLogsFilter = ""; + private String subscriptionLogsFilter = ""; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public String getTopicLogsFilter() { + return topicLogsFilter; + } + + public void setTopicLogsFilter(String topicLogsFilter) { + this.topicLogsFilter = topicLogsFilter; + } + + public String getSubscriptionLogsFilter() { + return subscriptionLogsFilter; + } + + public void setSubscriptionLogsFilter(String subscriptionLogsFilter) { + this.subscriptionLogsFilter = subscriptionLogsFilter; + } + } }