feat: unit tests for Synthetics Card, update ProblemsList tests
Signed-off-by: Isaiah Thiessen <isaiah.thiessen@telus.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/plugin-dynatrace': minor
|
||||
---
|
||||
|
||||
New features:
|
||||
|
||||
- Some visual improvements to the table that displays Problems
|
||||
- Added support for viewing recent Synthetics results using
|
||||
- Added some additional linking to the configured Dynatrace instance
|
||||
@@ -38,6 +38,8 @@ dynatrace:
|
||||
|
||||
#### Catalog Configuration
|
||||
|
||||
##### View Recent Application Problems
|
||||
|
||||
To show information from Dynatrace for a catalog entity, add the following annotation to `catalog-info.yaml`:
|
||||
|
||||
```yaml
|
||||
@@ -51,6 +53,23 @@ metadata:
|
||||
|
||||
The `DYNATRACE_ENTITY_ID` can be found in Dynatrace by browsing to the entity (a service, synthetic, frontend, workload, etc.). It will be located in the browser address bar in the `id` parameter and has the format `ENTITY_TYPE-ENTITY_ID`, where `ENTITY_TYPE` will be one of `SERVICE`, `SYNTHETIC_TEST`, or other, and `ENTITY_ID` will be a string of characters containing uppercase letters and numbers.
|
||||
|
||||
##### Viewing Recent Synthetics Results
|
||||
|
||||
To show recent results from a Synthetic Monitor, add the following annotation to `catalog-info.yaml`:
|
||||
|
||||
```yaml
|
||||
# catalog-info.yaml
|
||||
# [...]
|
||||
metadata:
|
||||
annotations:
|
||||
dynatrace.com/dynatrace-synthetics-ids: SYNTHETIC_ID, SYNTHETIC_ID_2, ...
|
||||
# [...]
|
||||
```
|
||||
|
||||
The annotation can also contain a comma separated list of Synthetic Ids to surface details for multiple monitors!
|
||||
|
||||
The `SYNTHETIC_ID` can be found in Dynatrace by browsing to the Synthetic monitor. It will be located in the browser address bar in the resource path - `https://example.dynatrace.com/ui/http-monitor/HTTP_CHECK-1234` for an Http check, or `https://example.dynatrace.com/ui/browser-monitor/SYNTHETIC_TEST-1234` for a browser clickpath.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This plugin is not officially supported by Dynatrace.
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
@@ -17,7 +17,12 @@ import React from 'react';
|
||||
import { Grid, Typography } from '@material-ui/core';
|
||||
import EmptyStateImage from '../../assets/emptystate.svg';
|
||||
|
||||
export const EmptyState = ({ message }) => {
|
||||
type EmptyStateProps = {
|
||||
message: string;
|
||||
};
|
||||
|
||||
export const EmptyState = (props: EmptyStateProps) => {
|
||||
const { message } = props;
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
|
||||
@@ -36,20 +36,26 @@ describe('ProblemStatus', () => {
|
||||
.mockResolvedValue({ problems });
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<ProblemsList dynatraceEntityId="example-service-3" />
|
||||
<ProblemsList
|
||||
dynatraceEntityId="__service_id__"
|
||||
dynatraceBaseUrl="__dynatrace__"
|
||||
/>
|
||||
</ApiProvider>,
|
||||
);
|
||||
expect(await rendered.findByText('example-service')).toBeInTheDocument();
|
||||
});
|
||||
it('renders "nothing to report :)" if no problems are found', async () => {
|
||||
it('returns "No Problems to Report!" if no problems are found', async () => {
|
||||
mockDynatraceApi.getDynatraceProblems = jest.fn().mockResolvedValue({});
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<ProblemsList dynatraceEntityId="example-service-3" />
|
||||
<ProblemsList
|
||||
dynatraceEntityId="example-service-3"
|
||||
dynatraceBaseUrl="__dynatrace__"
|
||||
/>
|
||||
</ApiProvider>,
|
||||
);
|
||||
expect(
|
||||
await rendered.findByText('Nothing to report :)'),
|
||||
await rendered.findByText('No Problems to Report!'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,6 +28,17 @@ type ProblemsListProps = {
|
||||
dynatraceBaseUrl: string;
|
||||
};
|
||||
|
||||
const cardContents = (problems: Array<any>, dynatraceBaseUrl: string) => {
|
||||
return problems?.length ? (
|
||||
<ProblemsTable
|
||||
problems={problems || []}
|
||||
dynatraceBaseUrl={dynatraceBaseUrl}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState message="No Problems to Report!" />
|
||||
);
|
||||
};
|
||||
|
||||
export const ProblemsList = (props: ProblemsListProps) => {
|
||||
const { dynatraceEntityId, dynatraceBaseUrl } = props;
|
||||
const dynatraceApi = useApi(dynatraceApiRef);
|
||||
@@ -50,14 +61,7 @@ export const ProblemsList = (props: ProblemsListProps) => {
|
||||
link: `${dynatraceBaseUrl}/#serviceOverview;id=${dynatraceEntityId}`,
|
||||
}}
|
||||
>
|
||||
{value?.totalCount ? (
|
||||
<ProblemsTable
|
||||
problems={problems || []}
|
||||
dynatraceBaseUrl={dynatraceBaseUrl}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState message="No Problems to Report!" />
|
||||
)}
|
||||
{cardContents(problems, dynatraceBaseUrl)}
|
||||
</InfoCard>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,17 +28,9 @@ describe('ProblemsTable', () => {
|
||||
it('renders the table with some problem data', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<ProblemsTable problems={problems} />,
|
||||
<ProblemsTable problems={problems} dynatraceBaseUrl="__dynatrace__" />,
|
||||
</ApiProvider>,
|
||||
);
|
||||
expect(await rendered.findByText('example-service')).toBeInTheDocument();
|
||||
});
|
||||
it('renders an empty table when no data is provided', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<ProblemsTable problems={[]} />
|
||||
</ApiProvider>,
|
||||
);
|
||||
expect(await rendered.findByText('Problems')).toBeInTheDocument();
|
||||
expect(await rendered.findByTitle('Search')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,5 +14,43 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SyntheticsCard } from './SyntheticsCard';
|
||||
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
|
||||
import { dynatraceApiRef } from '../../../api';
|
||||
import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
describe('SyntheticsCard', () => {});
|
||||
const mockDynatraceApi = {
|
||||
getDynatraceSyntheticFailures: jest.fn(),
|
||||
};
|
||||
const apis = TestApiRegistry.from(
|
||||
[dynatraceApiRef, mockDynatraceApi],
|
||||
[configApiRef, new ConfigReader({ dynatrace: { baseUrl: '__dynatrace__' } })],
|
||||
);
|
||||
|
||||
describe('SyntheticsCard', () => {
|
||||
it('renders the card with Synthetics data', async () => {
|
||||
mockDynatraceApi.getDynatraceSyntheticFailures = jest
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
locationsExecutionResults: [
|
||||
{
|
||||
locationId: '__location__',
|
||||
requestResults: [{ startTimestamp: 0 }],
|
||||
},
|
||||
],
|
||||
});
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<SyntheticsCard
|
||||
syntheticsId="HTTP_CHECK-1234"
|
||||
dynatraceBaseUrl="__dynatrace__"
|
||||
/>
|
||||
,
|
||||
</ApiProvider>,
|
||||
);
|
||||
expect(
|
||||
await rendered.findByText('View this Synthetic in Dynatrace'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
+48
-1
@@ -14,5 +14,52 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { SyntheticsLocation } from './SyntheticsLocation';
|
||||
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
|
||||
import { dynatraceApiRef } from '../../../api';
|
||||
import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
describe('SyntheticsLocation', () => {});
|
||||
const mockDynatraceApi = {
|
||||
getDynatraceSyntheticLocationInfo: jest.fn(),
|
||||
};
|
||||
const apis = TestApiRegistry.from(
|
||||
[dynatraceApiRef, mockDynatraceApi],
|
||||
[configApiRef, new ConfigReader({ dynatrace: { baseUrl: '__dynatrace__' } })],
|
||||
);
|
||||
|
||||
describe('SyntheticsLocation', () => {
|
||||
it('renders the SyntheticsLocation chip - recent failure', async () => {
|
||||
mockDynatraceApi.getDynatraceSyntheticLocationInfo = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ name: '__location__' });
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<SyntheticsLocation
|
||||
lastFailedTimestamp={new Date()}
|
||||
locationId="__location_id__"
|
||||
key="__key__"
|
||||
/>
|
||||
,
|
||||
</ApiProvider>,
|
||||
);
|
||||
expect(await rendered.findByText(/failed/)).toBeInTheDocument();
|
||||
});
|
||||
it('renders the SyntheticsLocation chip - no failures', async () => {
|
||||
mockDynatraceApi.getDynatraceSyntheticLocationInfo = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ name: '__location__' });
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<SyntheticsLocation
|
||||
lastFailedTimestamp={new Date(0)}
|
||||
locationId="__location_id__"
|
||||
key="__key__"
|
||||
/>
|
||||
,
|
||||
</ApiProvider>,
|
||||
);
|
||||
expect(await rendered.findByText(/__location__/)).toBeInTheDocument();
|
||||
expect(await rendered.queryByText(/failed/)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
+9
-15
@@ -28,26 +28,20 @@ type SyntheticsLocationProps = {
|
||||
key: string;
|
||||
};
|
||||
|
||||
const failedInLast24Hours = (timestamp: Date): Boolean => {
|
||||
return timestamp > new Date(new Date().getTime() - 1000 * 60 * 60 * 24);
|
||||
};
|
||||
|
||||
const failedInLast6Hours = (timestamp: Date): Boolean => {
|
||||
return timestamp > new Date(new Date().getTime() - 1000 * 60 * 60 * 6);
|
||||
};
|
||||
|
||||
const failedinLastHour = (timestamp: Date): Boolean => {
|
||||
return timestamp > new Date(new Date().getTime() - 1000 * 60 * 60);
|
||||
const failedInLastXHours = (timestamp: Date, offset: number): Boolean => {
|
||||
if (offset < 0 || offset > 24)
|
||||
throw new Error('offset must be between 0 and 24');
|
||||
return timestamp > new Date(new Date().getTime() - 1000 * 60 * 60 * offset);
|
||||
};
|
||||
|
||||
const chipColor = (timestamp: Date): string => {
|
||||
if (failedinLastHour(timestamp)) {
|
||||
if (failedInLastXHours(timestamp, 1)) {
|
||||
return 'salmon';
|
||||
}
|
||||
if (failedInLast6Hours(timestamp)) {
|
||||
if (failedInLastXHours(timestamp, 6)) {
|
||||
return 'sandybrown';
|
||||
}
|
||||
if (failedInLast24Hours(timestamp)) {
|
||||
if (failedInLastXHours(timestamp, 24)) {
|
||||
return 'palegoldenrod';
|
||||
}
|
||||
return 'lightgreen';
|
||||
@@ -71,8 +65,8 @@ export const SyntheticsLocation = (props: SyntheticsLocationProps) => {
|
||||
return (
|
||||
<Chip
|
||||
label={`${value?.name}${
|
||||
failedInLast24Hours(new Date(lastFailedTimestamp))
|
||||
? `: failed @ ${lastFailedTimestamp.toLocaleTimeString('en-CA')}`
|
||||
failedInLastXHours(new Date(lastFailedTimestamp), 24)
|
||||
? `: failed @ ${lastFailedTimestamp.toLocaleTimeString()}`
|
||||
: ''
|
||||
}`}
|
||||
size="medium"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"totalCount": 0,
|
||||
"totalCount": 1,
|
||||
"pageSize": 0,
|
||||
"nextPageKey": "AQAAABQBAAAABQ==",
|
||||
"problems": [
|
||||
|
||||
Reference in New Issue
Block a user