Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SKYEDEN-3082 tracker link in console #1940

Merged
merged 13 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/docs/configuration/message-tracking.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,8 @@ LogRepository logRepository(Client client) {
return new ElasticsearchLogRepository(client);
}
```

### UI configuration
Ui console can be configured to show tracking urls to users for topics and subscriptions.
To enable this, make bean implementing `pl.allegro.tech.hermes.tracker.management.TrackingUrlProvider`
available in Spring context.
12 changes: 10 additions & 2 deletions hermes-console/json-server/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@
},
"jsonToAvroDryRun": false,
"ack": "LEADER",
"trackingEnabled": false,
"trackingEnabled": true,
"migratedFromJsonType": false,
"schemaIdAwareSerializationEnabled": false,
"contentType": "AVRO",
Expand Down Expand Up @@ -251,6 +251,14 @@
"throughput": "0.0"
}
],
"topicsTrackingUrls": [
{"name": "Tracking Link 1", "url": "#"},
{"name": "Tracking Link 2", "url": "#"}
],
"subscriptionsTrackingUrls": [
{"name": "Tracking Link 1", "url": "#"},
{"name": "Tracking Link 2", "url": "#"}
],
"topicsOwners": [
{
"id": "41",
Expand Down Expand Up @@ -393,7 +401,7 @@
"retryClientErrors": true,
"backoffMaxIntervalMillis": 600000
},
"trackingEnabled": false,
"trackingEnabled": true,
"trackingMode": "trackingOff",
"owner": {
"source": "Service Catalog",
Expand Down
2 changes: 2 additions & 0 deletions hermes-console/json-server/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"/owners/sources/Service%20Catalog/:id": "/topicsOwners/:id",
"/readiness/datacenters": "/readinessDatacenters",
"/topics": "/topicNames",
"/tracking-urls/topics/:topicName": "/topicsTrackingUrls",
"/tracking-urls/topics/:topicName/subscriptions/:subscriptionName": "/subscriptionsTrackingUrls",
"/topics/:id/metrics": "/topicsMetrics/:id",
"/topics/:id/preview": "/topicPreview",
"/topics/:id/offline-clients-source": "/offlineClientsSource",
Expand Down
16 changes: 16 additions & 0 deletions hermes-console/src/api/hermes-client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import type { Stats } from '@/api/stats';
import type { SubscriptionHealth } from '@/api/subscription-health';
import type { SubscriptionMetrics } from '@/api/subscription-metrics';
import type { TopicForm } from '@/composables/topic/use-form-topic/types';
import type { TrackingUrl } from '@/api/tracking-url';

const acceptHeader = 'Accept';
const contentTypeHeader = 'Content-Type';
Expand Down Expand Up @@ -190,6 +191,21 @@ export function fetchOfflineClientsSource(
);
}

export function getTopicTrackingUrls(
topicName: string,
): ResponsePromise<TrackingUrl[]> {
return axios.get<TrackingUrl[]>(`/tracking-urls/topics/${topicName}`);
}

export function getSubscriptionTrackingUrls(
topicName: string,
subscriptionName: string,
): ResponsePromise<TrackingUrl[]> {
return axios.get<TrackingUrl[]>(
`/tracking-urls/topics/${topicName}/subscriptions/${subscriptionName}`,
);
}

export function fetchTopicClients(
topicName: string,
): ResponsePromise<string[]> {
Expand Down
4 changes: 4 additions & 0 deletions hermes-console/src/api/tracking-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface TrackingUrl {
name: string;
url: string;
}
43 changes: 43 additions & 0 deletions hermes-console/src/components/tracking-card/TrackingCard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { expect } from 'vitest';
import { render } from '@/utils/test-utils';
import TrackingCard from '@/components/tracking-card/TrackingCard.vue';

describe('TrackingCard', () => {
const props = {
trackingUrls: [
{ name: 'url1', url: 'https://test-tracking-url1' },
{ name: 'url2', url: 'https://test-tracking-url2' },
],
};

it('should render title properly', () => {
// when
const { getByText } = render(TrackingCard, { props });

// then
const row = getByText('trackingCard.title');
expect(row).toBeVisible();
});

it('should render all tracking urls', () => {
// when
const { container } = render(TrackingCard, { props });

// then
const elements = container.querySelectorAll('a')!!;
expect(elements[0]).toHaveAttribute('href', 'https://test-tracking-url1');
expect(elements[0]).toHaveTextContent('url1');
expect(elements[1]).toHaveAttribute('href', 'https://test-tracking-url2');
expect(elements[1]).toHaveTextContent('url2');
});

it('should render message when no tracking urls', () => {
// given
const emptyProps = { trackingUrls: [] };
const { getByText } = render(TrackingCard, { emptyProps });

// then
const row = getByText('trackingCard.noTrackingUrls');
expect(row).toBeVisible();
});
});
34 changes: 34 additions & 0 deletions hermes-console/src/components/tracking-card/TrackingCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script setup lang="ts">
import type { TrackingUrl } from '@/api/tracking-url';

const props = defineProps<{
trackingUrls: TrackingUrl[];
}>();
</script>

<template>
<v-card>
<template #title>
<div class="d-flex justify-space-between">
<p class="font-weight-bold">
{{ $t('trackingCard.title') }}
</p>
</div>
</template>
<v-card-item v-if="props.trackingUrls && props.trackingUrls.length > 0">
<p v-for="trackingUrl in props.trackingUrls" :key="trackingUrl.name">
<v-btn
:href="trackingUrl.url"
target="_blank"
variant="text"
color="blue"
>
{{ trackingUrl.name }}
</v-btn>
</p>
</v-card-item>
<v-card-item v-else> {{ $t('trackingCard.noTrackingUrls') }} </v-card-item>
</v-card>
</template>

<style scoped lang="scss"></style>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
fetchSubscriptionHealth as getSubscriptionHealth,
fetchSubscriptionLastUndeliveredMessage as getSubscriptionLastUndeliveredMessage,
fetchSubscriptionMetrics as getSubscriptionMetrics,
getSubscriptionTrackingUrls,
fetchSubscriptionUndeliveredMessages as getSubscriptionUndeliveredMessages,
retransmitSubscriptionMessages,
suspendSubscription as suspend,
Expand All @@ -20,6 +21,7 @@ import type { SentMessageTrace } from '@/api/subscription-undelivered';
import type { Subscription } from '@/api/subscription';
import type { SubscriptionHealth } from '@/api/subscription-health';
import type { SubscriptionMetrics } from '@/api/subscription-metrics';
import type { TrackingUrl } from '@/api/tracking-url';

export interface UseSubscription {
subscription: Ref<Subscription | undefined>;
Expand All @@ -28,6 +30,7 @@ export interface UseSubscription {
subscriptionHealth: Ref<SubscriptionHealth | undefined>;
subscriptionUndeliveredMessages: Ref<SentMessageTrace[] | null>;
subscriptionLastUndeliveredMessage: Ref<SentMessageTrace | null>;
trackingUrls: Ref<TrackingUrl[] | undefined>;
loading: Ref<boolean>;
error: Ref<UseSubscriptionsErrors>;
removeSubscription: () => Promise<boolean>;
Expand All @@ -44,6 +47,7 @@ export interface UseSubscriptionsErrors {
fetchSubscriptionHealth: Error | null;
fetchSubscriptionUndeliveredMessages: Error | null;
fetchSubscriptionLastUndeliveredMessage: Error | null;
getSubscriptionTrackingUrls: Error | null;
}

export function useSubscription(
Expand All @@ -58,6 +62,7 @@ export function useSubscription(
const subscriptionHealth = ref<SubscriptionHealth>();
const subscriptionUndeliveredMessages = ref<SentMessageTrace[]>([]);
const subscriptionLastUndeliveredMessage = ref<SentMessageTrace | null>(null);
const trackingUrls = ref<TrackingUrl[]>();
const loading = ref(false);
const error = ref<UseSubscriptionsErrors>({
fetchSubscription: null,
Expand All @@ -66,6 +71,7 @@ export function useSubscription(
fetchSubscriptionHealth: null,
fetchSubscriptionUndeliveredMessages: null,
fetchSubscriptionLastUndeliveredMessage: null,
getSubscriptionTrackingUrls: null,
});

const fetchSubscription = async () => {
Expand Down Expand Up @@ -150,6 +156,16 @@ export function useSubscription(
}
};

const fetchSubscriptionTrackingUrls = async () => {
try {
trackingUrls.value = (
await getSubscriptionTrackingUrls(topicName, subscriptionName)
).data;
} catch (e) {
error.value.getSubscriptionTrackingUrls = e as Error;
}
};

const removeSubscription = async (): Promise<boolean> => {
try {
await deleteSubscription(topicName, subscriptionName);
Expand Down Expand Up @@ -278,6 +294,7 @@ export function useSubscription(
};

fetchSubscription();
fetchSubscriptionTrackingUrls();

return {
subscription,
Expand All @@ -286,6 +303,7 @@ export function useSubscription(
subscriptionHealth,
subscriptionUndeliveredMessages,
subscriptionLastUndeliveredMessage,
trackingUrls,
loading,
error,
removeSubscription,
Expand Down
16 changes: 16 additions & 0 deletions hermes-console/src/composables/topic/use-topic/useTopic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
fetchOwner as getTopicOwner,
fetchTopicSubscriptionDetails as getTopicSubscriptionDetails,
fetchTopicSubscriptions as getTopicSubscriptions,
getTopicTrackingUrls,
} from '@/api/hermes-client';
import { dispatchErrorNotification } from '@/utils/notification-utils';
import { ref } from 'vue';
Expand All @@ -24,6 +25,7 @@ import type { OfflineClientsSource } from '@/api/offline-clients-source';
import type { Owner } from '@/api/owner';
import type { Ref } from 'vue';
import type { Subscription } from '@/api/subscription';
import type { TrackingUrl } from '@/api/tracking-url';

export interface UseTopic {
topic: Ref<TopicWithSchema | undefined>;
Expand All @@ -32,6 +34,7 @@ export interface UseTopic {
metrics: Ref<TopicMetrics | undefined>;
subscriptions: Ref<Subscription[] | undefined>;
offlineClientsSource: Ref<OfflineClientsSource | undefined>;
trackingUrls: Ref<TrackingUrl[] | undefined>;
loading: Ref<boolean>;
error: Ref<UseTopicErrors>;
fetchOfflineClientsSource: () => Promise<void>;
Expand All @@ -46,6 +49,7 @@ export interface UseTopicErrors {
fetchTopicMetrics: Error | null;
fetchSubscriptions: Error | null;
fetchOfflineClientsSource: Error | null;
getTopicTrackingUrls: Error | null;
}

export function useTopic(topicName: string): UseTopic {
Expand All @@ -57,6 +61,7 @@ export function useTopic(topicName: string): UseTopic {
const metrics = ref<TopicMetrics>();
const subscriptions = ref<Subscription[]>();
const offlineClientsSource = ref<OfflineClientsSource>();
const trackingUrls = ref<TrackingUrl[]>();
const loading = ref(false);
const error = ref<UseTopicErrors>({
fetchTopic: null,
Expand All @@ -65,6 +70,7 @@ export function useTopic(topicName: string): UseTopic {
fetchTopicMetrics: null,
fetchSubscriptions: null,
fetchOfflineClientsSource: null,
getTopicTrackingUrls: null,
});

const fetchTopic = async () => {
Expand Down Expand Up @@ -152,6 +158,14 @@ export function useTopic(topicName: string): UseTopic {
}
};

const fetchTopicTrackingUrls = async () => {
try {
trackingUrls.value = (await getTopicTrackingUrls(topicName)).data;
} catch (e) {
error.value.getTopicTrackingUrls = e as Error;
}
};

const removeTopic = async (): Promise<boolean> => {
try {
await deleteTopic(topicName);
Expand Down Expand Up @@ -188,6 +202,7 @@ export function useTopic(topicName: string): UseTopic {
};

fetchTopic();
fetchTopicTrackingUrls();

return {
topic,
Expand All @@ -196,6 +211,7 @@ export function useTopic(topicName: string): UseTopic {
metrics,
subscriptions,
offlineClientsSource,
trackingUrls,
loading,
error,
fetchOfflineClientsSource,
Expand Down
6 changes: 6 additions & 0 deletions hermes-console/src/dummy/tracking-urls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { TrackingUrl } from '@/api/tracking-url';

export const dummyTrackingUrls: TrackingUrl[] = [
{ name: 'url1', url: 'https://test-url1' },
{ name: 'url2', url: 'https://test-url2' },
];
4 changes: 4 additions & 0 deletions hermes-console/src/i18n/en-US/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,10 @@ const en_US = {
title: 'Costs',
detailsButton: 'DASHBOARD',
},
trackingCard: {
title: 'Tracking',
noTrackingUrls: 'No tracking urls available',
},
};

export default en_US;
25 changes: 25 additions & 0 deletions hermes-console/src/views/subscription/SubscriptionView.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
dummyUndeliveredMessage,
dummyUndeliveredMessages,
} from '@/dummy/subscription';
import { dummyTrackingUrls } from '@/dummy/tracking-urls';
import { fireEvent } from '@testing-library/vue';
import { render } from '@/utils/test-utils';
import { Role } from '@/api/role';
Expand All @@ -35,13 +36,15 @@ const useSubscriptionStub: ReturnType<typeof useSubscription> = {
subscriptionHealth: ref(dummySubscriptionHealth),
subscriptionUndeliveredMessages: ref(dummyUndeliveredMessages),
subscriptionLastUndeliveredMessage: ref(dummyUndeliveredMessage),
trackingUrls: ref(dummyTrackingUrls),
error: ref({
fetchSubscription: null,
fetchOwner: null,
fetchSubscriptionMetrics: null,
fetchSubscriptionHealth: null,
fetchSubscriptionUndeliveredMessages: null,
fetchSubscriptionLastUndeliveredMessage: null,
getSubscriptionTrackingUrls: null,
}),
loading: computed(() => false),
removeSubscription: () => Promise.resolve(true),
Expand Down Expand Up @@ -349,4 +352,26 @@ describe('SubscriptionView', () => {
// then
expect(queryByText('costsCard.title')).not.toBeInTheDocument();
});

it('should render tracking card when tracking is enabled', () => {
// given
const dummySubscription2 = dummySubscription;
dummySubscription2.trackingEnabled = true;

// and
vi.mocked(useSubscription).mockReturnValueOnce({
...useSubscriptionStub,
subscription: ref(dummySubscription2),
});
vi.mocked(useRoles).mockReturnValueOnce(useRolesStub);
vi.mocked(useMetrics).mockReturnValueOnce(useMetricsStub);

// when
const { getByText } = render(SubscriptionView, {
testPinia: createTestingPiniaWithState(),
});

// then
expect(getByText('trackingCard.title')).toBeVisible();
});
});
Loading
Loading