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

Fixes #19860: Added Diagnostic tab to display diagnostic information || Added Sync offset button #19980

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,18 @@ test('Pipeline Alert', async ({ page }) => {
});
});

await test.step('Verify diagnostic info tab', async () => {
await visitObservabilityAlertPage(page);
await visitAlertDetailsPage(page, data.alertDetails);

const diagnosticTab = page.getByRole('tab', { name: /diagnostic info/i });
const diagnosticInfoResponse = page.waitForResponse(
`/api/v1/events/subscriptions/**/diagnosticInfo`
);
await diagnosticTab.click();
await diagnosticInfoResponse;
});

await test.step('Check created alert details', async () => {
await visitObservabilityAlertPage(page);
await visitAlertDetailsPage(page, data.alertDetails);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright 2024 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface DiagnosticItem {
key: string;
value: string | number | boolean;
description: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2025 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { act, render, screen } from '@testing-library/react';
import React from 'react';
import {
mockDiagnosticData,
mockEmptyDiagnosticData,
} from '../../../../mocks/Alerts.mock';
import { getDiagnosticInfo } from '../../../../rest/observabilityAPI';
import AlertDiagnosticInfoTab from './AlertDiagnosticInfoTab';

// Mock the API call
jest.mock('../../../../rest/observabilityAPI', () => ({
getDiagnosticInfo: jest.fn(),
}));

jest.mock('../../../../hooks/useFqn', () => ({
useFqn: jest.fn().mockReturnValue({ fqn: 'test-fqn' }),
}));

describe('AlertDiagnosticInfoTab', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should render all offset information correctly', async () => {
(getDiagnosticInfo as jest.Mock).mockResolvedValue(mockDiagnosticData);

await act(async () => {
render(<AlertDiagnosticInfoTab />);
});

// Check labels
expect(screen.getByText('label.latest-offset:')).toBeInTheDocument();
expect(screen.getByText('label.current-offset:')).toBeInTheDocument();
expect(screen.getByText('label.starting-offset:')).toBeInTheDocument();
expect(
screen.getByText('label.successful-event-plural:')
).toBeInTheDocument();
expect(screen.getByText('label.failed-event-plural:')).toBeInTheDocument();
expect(
screen.getByText('label.processed-all-event-plural:')
).toBeInTheDocument();

// Check values
expect(screen.getByText('100')).toBeInTheDocument();
expect(screen.getByText('80')).toBeInTheDocument();
expect(screen.getByText('0')).toBeInTheDocument();
expect(screen.getByText('75')).toBeInTheDocument();
expect(screen.getByText('5')).toBeInTheDocument();
expect(screen.getByText('Yes')).toBeInTheDocument();
});

it('should render processed all events status as "No" when false', async () => {
(getDiagnosticInfo as jest.Mock).mockResolvedValue(mockEmptyDiagnosticData);

await act(async () => {
render(<AlertDiagnosticInfoTab />);
});

expect(screen.getByText('No')).toBeInTheDocument();
});

it('should render with empty/zero values correctly', async () => {
(getDiagnosticInfo as jest.Mock).mockResolvedValue(mockEmptyDiagnosticData);

await act(async () => {
render(<AlertDiagnosticInfoTab />);
});

// Check numeric fields have value "0"
const zeroValues = screen.getAllByText('0');

expect(zeroValues).toHaveLength(5); // Should find 5 zero values
});

it('should handle API error correctly', async () => {
const error = new Error('API Error');
(getDiagnosticInfo as jest.Mock).mockRejectedValue(error);

await act(async () => {
render(<AlertDiagnosticInfoTab />);
});

expect(screen.queryByText('100')).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright 2024 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { InfoCircleOutlined } from '@ant-design/icons';
import { Card, Col, Row, Skeleton, Tooltip, Typography } from 'antd';
import { AxiosError } from 'axios';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { GRAYED_OUT_COLOR } from '../../../../constants/constants';
import { EventSubscriptionDiagnosticInfo } from '../../../../generated/events/api/eventSubscriptionDiagnosticInfo';
import { useFqn } from '../../../../hooks/useFqn';
import { getDiagnosticInfo } from '../../../../rest/observabilityAPI';
import { getDiagnosticItems } from '../../../../utils/Alerts/AlertsUtil';
import { showErrorToast } from '../../../../utils/ToastUtils';

function AlertDiagnosticInfoTab() {
const { Text } = Typography;
const { fqn } = useFqn();
const [diagnosticData, setDiagnosticData] =
useState<EventSubscriptionDiagnosticInfo>();
const [diagnosticIsLoading, setDiagnosticIsLoading] = useState(true);

const fetchDiagnosticInfo = useCallback(async () => {
try {
setDiagnosticIsLoading(true);
const diagnosticInfoData = await getDiagnosticInfo(fqn);
setDiagnosticData(diagnosticInfoData);
} catch (error) {
showErrorToast(error as AxiosError);
} finally {
setDiagnosticIsLoading(false);
}
}, [fqn]);

useEffect(() => {
fetchDiagnosticInfo();
}, []);

const diagnosticItems = useMemo(
() => getDiagnosticItems(diagnosticData),
[diagnosticData]
);

const formatValue = (value: unknown): string => {
if (typeof value === 'boolean') {
return value ? 'Yes' : 'No';
}

return String(value);
};

return (
<Card>
<Skeleton active loading={diagnosticIsLoading} paragraph={{ rows: 3 }}>
<Row className="w-full" gutter={[16, 16]}>
{diagnosticItems.map((item) => (
<Col key={item.key} span={12}>
<Row align="middle">
<Col className="d-flex items-center" span={12}>
<Typography.Text className="d-flex items-center gap-1">
<Typography.Text className="m-0" type="secondary">
{`${item.key}:`}
</Typography.Text>
<Tooltip placement="bottom" title={item.description}>
<InfoCircleOutlined
className="info-icon"
style={{ color: GRAYED_OUT_COLOR }}
/>
</Tooltip>
</Typography.Text>
</Col>
<Col span={12}>
<Text>{formatValue(item.value)}</Text>
</Col>
</Row>
</Col>
))}
</Row>
</Skeleton>
</Card>
);
}

export default React.memo(AlertDiagnosticInfoTab);
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
export enum AlertDetailTabs {
CONFIGURATION = 'configuration',
RECENT_EVENTS = 'recentEvents',
DIAGNOSTIC_INFO = 'diagnostic info',
}

export enum AlertRecentEventFilters {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@
"credentials-type": "Anmeldeinformationstyp",
"criteria": "Kriterien",
"cron": "Cron",
"current-offset": "Aktueller Offset",
"current-version": "Current Version",
"custom": "Benutzerdefiniert",
"custom-attribute-plural": "Benutzerdefinierte Attribute",
Expand Down Expand Up @@ -379,6 +380,7 @@
"destination-plural": "Destinations",
"detail-plural": "Details",
"developed-by-developer": "Developed by {{developer}}",
"diagnostic-info": "Diagnoseinformationen",
"dimension": "Dimension",
"disable": "Deaktivieren",
"disable-lowercase": "disable",
Expand Down Expand Up @@ -482,6 +484,7 @@
"error-plural": "Errors",
"event-plural": "Events",
"event-publisher-plural": "Ereignisveröffentlicher",
"event-statistics": "Ereignisstatistiken",
"event-type": "Ereignistyp",
"event-type-lowercase": "event type",
"every": "Jede/r/s",
Expand All @@ -507,6 +510,7 @@
"external": "External",
"failed": "Fehlgeschlagen",
"failed-entity": "Failed {{entity}}",
"failed-event-plural": "Fehlgeschlagene Ereignisse",
"failing-subscription-id": "Failing Subscription Id",
"failure-comment": "Failure Comment",
"failure-context": "Fehlerkontext",
Expand Down Expand Up @@ -694,6 +698,7 @@
"last-run-result": "Ergebnis der letzten Ausführung",
"last-updated": "Zuletzt aktualisiert",
"latest": "Neueste",
"latest-offset": "Neuester Offset",
"layer": "Layer",
"layer-plural": "Layers",
"learn-more": "Learn More",
Expand Down Expand Up @@ -849,6 +854,7 @@
"observability-alert": "Observability Alert",
"october": "Oktober",
"of-lowercase": "von",
"offset-information": "Offset-Informationen",
"ok": "Ok",
"okta": "Okta",
"okta-service-account-email": "Okta-Dienstkonto-E-Mail",
Expand Down Expand Up @@ -951,6 +957,7 @@
"privacy-policy": "Privacy Policy",
"private-key": "Privater Schlüssel",
"private-key-id": "ID des privaten Schlüssels",
"processed-all-event-plural": "Alle Ereignisse verarbeitet",
"profile": "Profil",
"profile-config": "Profil-Konfiguration",
"profile-lowercase": "profil",
Expand Down Expand Up @@ -1011,6 +1018,7 @@
"relationship": "Relationship",
"relationship-type": "Relationship Type",
"relevance": "Relevanz",
"relevant-unprocessed-event-plural": "Relevante unverarbeitete Ereignisse",
"remove": "Entfernen",
"remove-entity": "{{entity}} entfernen",
"remove-entity-lowercase": "remove {{entity}}",
Expand Down Expand Up @@ -1192,6 +1200,7 @@
"start-entity": "{{entity}} gestartet",
"started": "Gestartet",
"started-following": "Hat begonnen zu folgen",
"starting-offset": "Start-Offset",
"status": "Status",
"stay-up-to-date": "Bleiben Sie auf dem neuesten Stand",
"step": "Step",
Expand All @@ -1212,6 +1221,7 @@
"subscription": "Subscription",
"success": "Erfolg",
"successful": "Successful",
"successful-event-plural": "Erfolgreiche Ereignisse",
"successfully-lowercase": "erfolgreich",
"successfully-uploaded": "Erfolgreich hochgeladen",
"suggest": "Vorschlagen",
Expand All @@ -1229,6 +1239,7 @@
"support": "Unterstützung",
"support-url": "Support-URL",
"supported-language-plural": "Supported Languages",
"sync-alert-offset": "Warnung synchronisieren",
"synonym-lowercase-plural": "Synonyme",
"synonym-plural": "Synonyme",
"table": "Tabelle",
Expand Down Expand Up @@ -1316,6 +1327,7 @@
"total": "Total",
"total-entity": "Gesamte {{entity}}",
"total-index-sent": "Gesamtindex gesendet",
"total-unprocessed-event-plural": "Gesamte unverarbeitete Ereignisse",
"total-user-plural": "Total Users",
"tour": "Tour",
"tracking": "Verfolgung",
Expand Down Expand Up @@ -1437,6 +1449,7 @@
"airflow-guide-message": "OpenMetadata verwendet Airflow, um Ingestion Connectors auszuführen. Wir haben verwaltete APIs entwickelt, um Ingestion Connectors bereitzustellen. Verwenden Sie bitte die OpenMetadata Airflow-Instanz oder beziehen Sie sich auf die unten stehende Anleitung, um die verwalteten APIs in Ihrer Airflow-Installation zu installieren.",
"airflow-host-ip-address": "OpenMetadata wird sich von der IP <0>{{hostIp}}</0> mit Ihrer Ressource verbinden. Stellen Sie sicher, dass eingehender Datenverkehr in Ihren Netzwerksicherheitseinstellungen zugelassen ist.",
"alert-recent-events-description": "List of recent alert events triggered for the alert {{alertName}}.",
"alert-synced-successfully": "Warnung erfolgreich synchronisiert",
"alerts-description": "Bleiben Sie mit zeitnahen Benachrichtigungen über Webhooks auf dem neuesten Stand.",
"alerts-destination-description": "Senden Sie Benachrichtigungen an Slack, MS Teams, E-Mail oder verwenden Sie Webhooks.",
"alerts-filter-description": "Geben Sie die Änderungsereignisse an, um den Umfang Ihrer Benachrichtigungen einzugrenzen.",
Expand Down Expand Up @@ -1506,6 +1519,7 @@
"created-this-task-lowercase": "hat diese Aufgabe erstellt",
"cron-dow-validation-failure": "Der DOW-Teil muss >= 0 und <= 6 sein",
"cron-less-than-hour-message": "Cron schedule too frequent. Please choose at least 1-hour intervals.",
"current-offset-description": "The current offset of the event subscription.",
"custom-classification-name-dbt-tags": "Benutzerdefinierter OpenMetadata-Klassifikationsname für dbt-Tags ",
"custom-favicon-url-path-message": "URL path for the favicon icon.",
"custom-logo-configuration-message": "Konfigurieren Sie das Anwendungslogo und das Monogramm.",
Expand Down Expand Up @@ -1612,6 +1626,7 @@
"explore-our-guide-here": "explore our guide here.",
"export-entity-help": "Laden Sie alle Ihre {{entity}} als CSV-Datei herunter und teilen Sie sie mit Ihrem Team.",
"external-destination-selection": "Only external destinations can be tested.",
"failed-events-description": "Count of failed events for specific alert.",
"failed-status-for-entity-deploy": "<0>{{entity}}</0> wurde {{entityStatus}}, aber das Bereitstellen ist fehlgeschlagen.",
"feed-asset-action-header": "{{action}} <0>data asset</0>",
"feed-custom-property-header": "updated Custom Properties on",
Expand Down Expand Up @@ -1678,6 +1693,7 @@
"kpi-target-achieved": "Herzlichen Glückwunsch zur Erreichung Ihrer Ziele!",
"kpi-target-achieved-before-time": "Fantastisch! Sie haben Ihr Ziel bereits weit vor der Zeit erreicht.",
"kpi-target-overdue": "Macht nichts. Es ist Zeit, Ihre Ziele neu zu strukturieren und schneller voranzukommen.",
"latest-offset-description": "The latest offset of the event in the system.",
"leave-the-team-team-name": "Verlassen Sie das Team {{teamName}}",
"length-validator-error": "Mindestens {{length}} {{field}} erforderlich.",
"lineage-ingestion-description": "Die Verbindungsdaten-Erfassung kann konfiguriert und bereitgestellt werden, nachdem eine Metadaten-Erfassung eingerichtet wurde. Der Verbindungsdaten-Erfassungsworkflow ruft den Abfrageverlauf ab, analysiert Abfragen wie CREATE, INSERT, MERGE usw. und bereitet die Verbindung zwischen den beteiligten Entitäten vor. Die Verbindungsdaten-Erfassung kann nur eine Pipeline für einen Datenbankdienst haben. Definieren Sie die Dauer des Abfrageprotokolls (in Tagen) und die Ergebnisgrenze, um zu starten.",
Expand Down Expand Up @@ -1850,6 +1866,7 @@
"popup-block-message": "The sign in pop-up was blocked by the browser. Please <0>enable</0> it and try again.",
"process-pii-sensitive-column-message": "Überprüfe die Spaltennamen, um PII-sensitive/nicht sensitive Spalten automatisch zu taggen.",
"process-pii-sensitive-column-message-profiler": "Wenn aktiviert, wird die Beispiel-Daten analysiert, um geeignete PII-Tags für jede Spalte zu bestimmen.",
"processed-all-events-description": "Indicates whether all events have been processed.",
"profile-sample-percentage-message": "Setze den Profiler-Wert als Prozentsatz",
"profile-sample-row-count-message": "Setze den Profiler-Wert als Zeilenanzahl",
"profiler-ingestion-description": "Ein Profiler-Workflow kann nach der Einrichtung einer Metadaten-Ingestion konfiguriert und bereitgestellt werden. Es können mehrere Profiler-Pipelines für denselben Datenbankdienst eingerichtet werden. Die Pipeline speist das Profiler-Tab der Tabellenentität und führt auch die für diese Entität konfigurierten Tests aus. Gib einen Namen, FQN und definiere das Filtermuster an, um zu starten.",
Expand Down Expand Up @@ -1914,8 +1931,10 @@
"sso-provider-not-supported": "SSO-Provider {{provider}} wird nicht unterstützt.",
"stage-file-location-message": "Temporärer Dateiname zum Speichern der Abfrageprotokolle vor der Verarbeitung. Es wird ein absoluter Dateipfad benötigt.",
"star-on-github-description": "Contribute with Stars to help data enthusiasts discover OpenMetadata!",
"starting-offset-description": "The initial offset of the event subscription when it started processing.",
"still-running-into-issue": "Wenn Sie immer noch auf Probleme stoßen, kontaktieren Sie uns bitte über Slack.",
"success-status-for-entity-deploy": "<0>{{entity}}</0> wurde {{entityStatus}} und erfolgreich bereitgestellt",
"successful-events-description": "Count of successful events for specific alert.",
"successfully-completed-the-tour": "Sie haben die Tour erfolgreich abgeschlossen.",
"synonym-placeholder": "Um ein Synonym hinzuzufügen, geben Sie es einfach ein und drücken Sie Enter",
"system-alert-edit-message": "Editing a system generated alert is not allowed.",
Expand Down
Loading