feat: unit tests for Synthetics Card, update ProblemsList tests

Signed-off-by: Isaiah Thiessen <isaiah.thiessen@telus.com>
This commit is contained in:
Isaiah Thiessen
2022-08-04 10:28:43 -07:00
parent 220669ef6b
commit e44c0b3811
11 changed files with 155 additions and 56 deletions
+9
View File
@@ -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
+19
View File
@@ -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();
});
});
@@ -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();
});
});
@@ -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 -1
View File
@@ -1,5 +1,5 @@
{
"totalCount": 0,
"totalCount": 1,
"pageSize": 0,
"nextPageKey": "AQAAABQBAAAABQ==",
"problems": [