Skip to content

Commit 829b93a

Browse files
authored
SKYEDEN-3082 tracker link in console (#1940)
* SKYEDEN-3082 | Add topic tracking section * SKYEDEN-3082 | Use separate endpoint for tracking urls * SKYEDEN-3082 | Add endpoint for tracking urls * SKYEDEN-3082 | Add tests for tracking card * SKYEDEN-3082 | Add tracking card test to topic view * SKYEDEN-3082 | Add tracking card for subscription view * SKYEDEN-3082 | Add docs for tracking urls ui * SKYEDEN-3082 | Change endpoint path * SKYEDEN-3082 | Remove * import
1 parent 64d5940 commit 829b93a

File tree

18 files changed

+295
-2
lines changed

18 files changed

+295
-2
lines changed

docs/docs/configuration/message-tracking.md

+5
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,8 @@ LogRepository logRepository(Client client) {
7373
return new ElasticsearchLogRepository(client);
7474
}
7575
```
76+
77+
### UI configuration
78+
Ui console can be configured to show tracking urls to users for topics and subscriptions.
79+
To enable this, make bean implementing `pl.allegro.tech.hermes.tracker.management.TrackingUrlProvider`
80+
available in Spring context.

hermes-console/json-server/db.json

+10-2
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@
213213
},
214214
"jsonToAvroDryRun": false,
215215
"ack": "LEADER",
216-
"trackingEnabled": false,
216+
"trackingEnabled": true,
217217
"migratedFromJsonType": false,
218218
"schemaIdAwareSerializationEnabled": false,
219219
"contentType": "AVRO",
@@ -251,6 +251,14 @@
251251
"throughput": "0.0"
252252
}
253253
],
254+
"topicsTrackingUrls": [
255+
{"name": "Tracking Link 1", "url": "#"},
256+
{"name": "Tracking Link 2", "url": "#"}
257+
],
258+
"subscriptionsTrackingUrls": [
259+
{"name": "Tracking Link 1", "url": "#"},
260+
{"name": "Tracking Link 2", "url": "#"}
261+
],
254262
"topicsOwners": [
255263
{
256264
"id": "41",
@@ -393,7 +401,7 @@
393401
"retryClientErrors": true,
394402
"backoffMaxIntervalMillis": 600000
395403
},
396-
"trackingEnabled": false,
404+
"trackingEnabled": true,
397405
"trackingMode": "trackingOff",
398406
"owner": {
399407
"source": "Service Catalog",

hermes-console/json-server/routes.json

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
"/owners/sources/Service%20Catalog/:id": "/topicsOwners/:id",
99
"/readiness/datacenters": "/readinessDatacenters",
1010
"/topics": "/topicNames",
11+
"/tracking-urls/topics/:topicName": "/topicsTrackingUrls",
12+
"/tracking-urls/topics/:topicName/subscriptions/:subscriptionName": "/subscriptionsTrackingUrls",
1113
"/topics/:id/metrics": "/topicsMetrics/:id",
1214
"/topics/:id/preview": "/topicPreview",
1315
"/topics/:id/offline-clients-source": "/offlineClientsSource",

hermes-console/src/api/hermes-client/index.ts

+16
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import type { Stats } from '@/api/stats';
4545
import type { SubscriptionHealth } from '@/api/subscription-health';
4646
import type { SubscriptionMetrics } from '@/api/subscription-metrics';
4747
import type { TopicForm } from '@/composables/topic/use-form-topic/types';
48+
import type { TrackingUrl } from '@/api/tracking-url';
4849

4950
const acceptHeader = 'Accept';
5051
const contentTypeHeader = 'Content-Type';
@@ -190,6 +191,21 @@ export function fetchOfflineClientsSource(
190191
);
191192
}
192193

194+
export function getTopicTrackingUrls(
195+
topicName: string,
196+
): ResponsePromise<TrackingUrl[]> {
197+
return axios.get<TrackingUrl[]>(`/tracking-urls/topics/${topicName}`);
198+
}
199+
200+
export function getSubscriptionTrackingUrls(
201+
topicName: string,
202+
subscriptionName: string,
203+
): ResponsePromise<TrackingUrl[]> {
204+
return axios.get<TrackingUrl[]>(
205+
`/tracking-urls/topics/${topicName}/subscriptions/${subscriptionName}`,
206+
);
207+
}
208+
193209
export function fetchTopicClients(
194210
topicName: string,
195211
): ResponsePromise<string[]> {
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface TrackingUrl {
2+
name: string;
3+
url: string;
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { expect } from 'vitest';
2+
import { render } from '@/utils/test-utils';
3+
import TrackingCard from '@/components/tracking-card/TrackingCard.vue';
4+
5+
describe('TrackingCard', () => {
6+
const props = {
7+
trackingUrls: [
8+
{ name: 'url1', url: 'https://test-tracking-url1' },
9+
{ name: 'url2', url: 'https://test-tracking-url2' },
10+
],
11+
};
12+
13+
it('should render title properly', () => {
14+
// when
15+
const { getByText } = render(TrackingCard, { props });
16+
17+
// then
18+
const row = getByText('trackingCard.title');
19+
expect(row).toBeVisible();
20+
});
21+
22+
it('should render all tracking urls', () => {
23+
// when
24+
const { container } = render(TrackingCard, { props });
25+
26+
// then
27+
const elements = container.querySelectorAll('a')!!;
28+
expect(elements[0]).toHaveAttribute('href', 'https://test-tracking-url1');
29+
expect(elements[0]).toHaveTextContent('url1');
30+
expect(elements[1]).toHaveAttribute('href', 'https://test-tracking-url2');
31+
expect(elements[1]).toHaveTextContent('url2');
32+
});
33+
34+
it('should render message when no tracking urls', () => {
35+
// given
36+
const emptyProps = { trackingUrls: [] };
37+
const { getByText } = render(TrackingCard, { emptyProps });
38+
39+
// then
40+
const row = getByText('trackingCard.noTrackingUrls');
41+
expect(row).toBeVisible();
42+
});
43+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<script setup lang="ts">
2+
import type { TrackingUrl } from '@/api/tracking-url';
3+
4+
const props = defineProps<{
5+
trackingUrls: TrackingUrl[];
6+
}>();
7+
</script>
8+
9+
<template>
10+
<v-card>
11+
<template #title>
12+
<div class="d-flex justify-space-between">
13+
<p class="font-weight-bold">
14+
{{ $t('trackingCard.title') }}
15+
</p>
16+
</div>
17+
</template>
18+
<v-card-item v-if="props.trackingUrls && props.trackingUrls.length > 0">
19+
<p v-for="trackingUrl in props.trackingUrls" :key="trackingUrl.name">
20+
<v-btn
21+
:href="trackingUrl.url"
22+
target="_blank"
23+
variant="text"
24+
color="blue"
25+
>
26+
{{ trackingUrl.name }}
27+
</v-btn>
28+
</p>
29+
</v-card-item>
30+
<v-card-item v-else> {{ $t('trackingCard.noTrackingUrls') }} </v-card-item>
31+
</v-card>
32+
</template>
33+
34+
<style scoped lang="scss"></style>

hermes-console/src/composables/subscription/use-subscription/useSubscription.ts

+18
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
fetchSubscriptionHealth as getSubscriptionHealth,
77
fetchSubscriptionLastUndeliveredMessage as getSubscriptionLastUndeliveredMessage,
88
fetchSubscriptionMetrics as getSubscriptionMetrics,
9+
getSubscriptionTrackingUrls,
910
fetchSubscriptionUndeliveredMessages as getSubscriptionUndeliveredMessages,
1011
retransmitSubscriptionMessages,
1112
suspendSubscription as suspend,
@@ -20,6 +21,7 @@ import type { SentMessageTrace } from '@/api/subscription-undelivered';
2021
import type { Subscription } from '@/api/subscription';
2122
import type { SubscriptionHealth } from '@/api/subscription-health';
2223
import type { SubscriptionMetrics } from '@/api/subscription-metrics';
24+
import type { TrackingUrl } from '@/api/tracking-url';
2325

2426
export interface UseSubscription {
2527
subscription: Ref<Subscription | undefined>;
@@ -28,6 +30,7 @@ export interface UseSubscription {
2830
subscriptionHealth: Ref<SubscriptionHealth | undefined>;
2931
subscriptionUndeliveredMessages: Ref<SentMessageTrace[] | null>;
3032
subscriptionLastUndeliveredMessage: Ref<SentMessageTrace | null>;
33+
trackingUrls: Ref<TrackingUrl[] | undefined>;
3134
loading: Ref<boolean>;
3235
error: Ref<UseSubscriptionsErrors>;
3336
removeSubscription: () => Promise<boolean>;
@@ -44,6 +47,7 @@ export interface UseSubscriptionsErrors {
4447
fetchSubscriptionHealth: Error | null;
4548
fetchSubscriptionUndeliveredMessages: Error | null;
4649
fetchSubscriptionLastUndeliveredMessage: Error | null;
50+
getSubscriptionTrackingUrls: Error | null;
4751
}
4852

4953
export function useSubscription(
@@ -58,6 +62,7 @@ export function useSubscription(
5862
const subscriptionHealth = ref<SubscriptionHealth>();
5963
const subscriptionUndeliveredMessages = ref<SentMessageTrace[]>([]);
6064
const subscriptionLastUndeliveredMessage = ref<SentMessageTrace | null>(null);
65+
const trackingUrls = ref<TrackingUrl[]>();
6166
const loading = ref(false);
6267
const error = ref<UseSubscriptionsErrors>({
6368
fetchSubscription: null,
@@ -66,6 +71,7 @@ export function useSubscription(
6671
fetchSubscriptionHealth: null,
6772
fetchSubscriptionUndeliveredMessages: null,
6873
fetchSubscriptionLastUndeliveredMessage: null,
74+
getSubscriptionTrackingUrls: null,
6975
});
7076

7177
const fetchSubscription = async () => {
@@ -150,6 +156,16 @@ export function useSubscription(
150156
}
151157
};
152158

159+
const fetchSubscriptionTrackingUrls = async () => {
160+
try {
161+
trackingUrls.value = (
162+
await getSubscriptionTrackingUrls(topicName, subscriptionName)
163+
).data;
164+
} catch (e) {
165+
error.value.getSubscriptionTrackingUrls = e as Error;
166+
}
167+
};
168+
153169
const removeSubscription = async (): Promise<boolean> => {
154170
try {
155171
await deleteSubscription(topicName, subscriptionName);
@@ -278,6 +294,7 @@ export function useSubscription(
278294
};
279295

280296
fetchSubscription();
297+
fetchSubscriptionTrackingUrls();
281298

282299
return {
283300
subscription,
@@ -286,6 +303,7 @@ export function useSubscription(
286303
subscriptionHealth,
287304
subscriptionUndeliveredMessages,
288305
subscriptionLastUndeliveredMessage,
306+
trackingUrls,
289307
loading,
290308
error,
291309
removeSubscription,

hermes-console/src/composables/topic/use-topic/useTopic.ts

+16
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
fetchOwner as getTopicOwner,
1010
fetchTopicSubscriptionDetails as getTopicSubscriptionDetails,
1111
fetchTopicSubscriptions as getTopicSubscriptions,
12+
getTopicTrackingUrls,
1213
} from '@/api/hermes-client';
1314
import { dispatchErrorNotification } from '@/utils/notification-utils';
1415
import { ref } from 'vue';
@@ -24,6 +25,7 @@ import type { OfflineClientsSource } from '@/api/offline-clients-source';
2425
import type { Owner } from '@/api/owner';
2526
import type { Ref } from 'vue';
2627
import type { Subscription } from '@/api/subscription';
28+
import type { TrackingUrl } from '@/api/tracking-url';
2729

2830
export interface UseTopic {
2931
topic: Ref<TopicWithSchema | undefined>;
@@ -32,6 +34,7 @@ export interface UseTopic {
3234
metrics: Ref<TopicMetrics | undefined>;
3335
subscriptions: Ref<Subscription[] | undefined>;
3436
offlineClientsSource: Ref<OfflineClientsSource | undefined>;
37+
trackingUrls: Ref<TrackingUrl[] | undefined>;
3538
loading: Ref<boolean>;
3639
error: Ref<UseTopicErrors>;
3740
fetchOfflineClientsSource: () => Promise<void>;
@@ -46,6 +49,7 @@ export interface UseTopicErrors {
4649
fetchTopicMetrics: Error | null;
4750
fetchSubscriptions: Error | null;
4851
fetchOfflineClientsSource: Error | null;
52+
getTopicTrackingUrls: Error | null;
4953
}
5054

5155
export function useTopic(topicName: string): UseTopic {
@@ -57,6 +61,7 @@ export function useTopic(topicName: string): UseTopic {
5761
const metrics = ref<TopicMetrics>();
5862
const subscriptions = ref<Subscription[]>();
5963
const offlineClientsSource = ref<OfflineClientsSource>();
64+
const trackingUrls = ref<TrackingUrl[]>();
6065
const loading = ref(false);
6166
const error = ref<UseTopicErrors>({
6267
fetchTopic: null,
@@ -65,6 +70,7 @@ export function useTopic(topicName: string): UseTopic {
6570
fetchTopicMetrics: null,
6671
fetchSubscriptions: null,
6772
fetchOfflineClientsSource: null,
73+
getTopicTrackingUrls: null,
6874
});
6975

7076
const fetchTopic = async () => {
@@ -152,6 +158,14 @@ export function useTopic(topicName: string): UseTopic {
152158
}
153159
};
154160

161+
const fetchTopicTrackingUrls = async () => {
162+
try {
163+
trackingUrls.value = (await getTopicTrackingUrls(topicName)).data;
164+
} catch (e) {
165+
error.value.getTopicTrackingUrls = e as Error;
166+
}
167+
};
168+
155169
const removeTopic = async (): Promise<boolean> => {
156170
try {
157171
await deleteTopic(topicName);
@@ -188,6 +202,7 @@ export function useTopic(topicName: string): UseTopic {
188202
};
189203

190204
fetchTopic();
205+
fetchTopicTrackingUrls();
191206

192207
return {
193208
topic,
@@ -196,6 +211,7 @@ export function useTopic(topicName: string): UseTopic {
196211
metrics,
197212
subscriptions,
198213
offlineClientsSource,
214+
trackingUrls,
199215
loading,
200216
error,
201217
fetchOfflineClientsSource,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { TrackingUrl } from '@/api/tracking-url';
2+
3+
export const dummyTrackingUrls: TrackingUrl[] = [
4+
{ name: 'url1', url: 'https://test-url1' },
5+
{ name: 'url2', url: 'https://test-url2' },
6+
];

hermes-console/src/i18n/en-US/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,10 @@ const en_US = {
838838
title: 'Costs',
839839
detailsButton: 'DASHBOARD',
840840
},
841+
trackingCard: {
842+
title: 'Tracking',
843+
noTrackingUrls: 'No tracking urls available',
844+
},
841845
};
842846

843847
export default en_US;

hermes-console/src/views/subscription/SubscriptionView.spec.ts

+25
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
dummyUndeliveredMessage,
1414
dummyUndeliveredMessages,
1515
} from '@/dummy/subscription';
16+
import { dummyTrackingUrls } from '@/dummy/tracking-urls';
1617
import { fireEvent } from '@testing-library/vue';
1718
import { render } from '@/utils/test-utils';
1819
import { Role } from '@/api/role';
@@ -35,13 +36,15 @@ const useSubscriptionStub: ReturnType<typeof useSubscription> = {
3536
subscriptionHealth: ref(dummySubscriptionHealth),
3637
subscriptionUndeliveredMessages: ref(dummyUndeliveredMessages),
3738
subscriptionLastUndeliveredMessage: ref(dummyUndeliveredMessage),
39+
trackingUrls: ref(dummyTrackingUrls),
3840
error: ref({
3941
fetchSubscription: null,
4042
fetchOwner: null,
4143
fetchSubscriptionMetrics: null,
4244
fetchSubscriptionHealth: null,
4345
fetchSubscriptionUndeliveredMessages: null,
4446
fetchSubscriptionLastUndeliveredMessage: null,
47+
getSubscriptionTrackingUrls: null,
4548
}),
4649
loading: computed(() => false),
4750
removeSubscription: () => Promise.resolve(true),
@@ -349,4 +352,26 @@ describe('SubscriptionView', () => {
349352
// then
350353
expect(queryByText('costsCard.title')).not.toBeInTheDocument();
351354
});
355+
356+
it('should render tracking card when tracking is enabled', () => {
357+
// given
358+
const dummySubscription2 = dummySubscription;
359+
dummySubscription2.trackingEnabled = true;
360+
361+
// and
362+
vi.mocked(useSubscription).mockReturnValueOnce({
363+
...useSubscriptionStub,
364+
subscription: ref(dummySubscription2),
365+
});
366+
vi.mocked(useRoles).mockReturnValueOnce(useRolesStub);
367+
vi.mocked(useMetrics).mockReturnValueOnce(useMetricsStub);
368+
369+
// when
370+
const { getByText } = render(SubscriptionView, {
371+
testPinia: createTestingPiniaWithState(),
372+
});
373+
374+
// then
375+
expect(getByText('trackingCard.title')).toBeVisible();
376+
});
352377
});

0 commit comments

Comments
 (0)