diff --git a/.changeset/fresh-rabbits-juggle.md b/.changeset/fresh-rabbits-juggle.md new file mode 100644 index 0000000000..804ae70853 --- /dev/null +++ b/.changeset/fresh-rabbits-juggle.md @@ -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 diff --git a/microsite/data/plugins/dynatrace.yaml b/microsite/data/plugins/dynatrace.yaml index 3ab85ae1eb..f204c4105c 100644 --- a/microsite/data/plugins/dynatrace.yaml +++ b/microsite/data/plugins/dynatrace.yaml @@ -3,7 +3,7 @@ title: Dynatrace author: TELUS authorUrl: https://github.com/telus category: Monitoring -description: View monitoring info from dynatrace for services in your software catalog. +description: View monitoring info from Dynatrace for services in your software catalog. documentation: https://github.com/backstage/backstage/tree/master/plugins/dynatrace iconUrl: img/dynatrace.svg npmPackageName: '@backstage/plugin-dynatrace' diff --git a/plugins/dynatrace/README.md b/plugins/dynatrace/README.md index 150c9768af..8ed72ebb5d 100644 --- a/plugins/dynatrace/README.md +++ b/plugins/dynatrace/README.md @@ -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/synthetics-ids: SYNTHETIC_ID, SYNTHETIC_ID_2, ... +# [...] +``` + +The annotation can also contain a comma or space 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. diff --git a/plugins/dynatrace/src/api/DynatraceApi.ts b/plugins/dynatrace/src/api/DynatraceApi.ts index 1865b20363..4cb5f105bf 100644 --- a/plugins/dynatrace/src/api/DynatraceApi.ts +++ b/plugins/dynatrace/src/api/DynatraceApi.ts @@ -35,8 +35,32 @@ export type DynatraceProblem = { affectedEntities: Array; }; +type SyntheticRequestResults = { + startTimestamp: number; +}; + +export interface DynatraceSyntheticResults { + monitorId: string; + locationsExecutionResults: [ + { + locationId: number; + executionId: string; + requestResults: Array; + }, + ]; +} + +export interface DynatraceSyntheticLocationInfo { + entityId: string; + name: string; + city: string; + browserType: string; +} + export interface DynatraceProblems { problems: Array; + totalCount: number; + pageSize: number; } export const dynatraceApiRef = createApiRef({ @@ -47,4 +71,10 @@ export type DynatraceApi = { getDynatraceProblems( dynatraceEntityId: string, ): Promise; + getDynatraceSyntheticFailures( + syntheticsId: string, + ): Promise; + getDynatraceSyntheticLocationInfo( + syntheticLocationId: string, + ): Promise; }; diff --git a/plugins/dynatrace/src/api/DynatraceClient.ts b/plugins/dynatrace/src/api/DynatraceClient.ts index 27476ddbac..a31253bcb5 100644 --- a/plugins/dynatrace/src/api/DynatraceClient.ts +++ b/plugins/dynatrace/src/api/DynatraceClient.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { DynatraceProblems, DynatraceApi } from './DynatraceApi'; +import { + DynatraceProblems, + DynatraceApi, + DynatraceSyntheticResults, + DynatraceSyntheticLocationInfo, +} from './DynatraceApi'; import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; export class DynatraceClient implements DynatraceApi { @@ -52,11 +57,37 @@ export class DynatraceClient implements DynatraceApi { ); } + async getDynatraceSyntheticFailures( + syntheticsId: string, + ): Promise { + if (!syntheticsId) { + throw new Error('Dynatrace syntheticId is required'); + } + + return this.callApi( + `synthetic/execution/${encodeURIComponent(syntheticsId)}/FAILED`, + {}, + ); + } + + async getDynatraceSyntheticLocationInfo( + syntheticLocationId: string, + ): Promise { + if (!syntheticLocationId) { + throw new Error('Dynatrace syntheticLocationId is required'); + } + + return this.callApi( + `synthetic/locations/${encodeURIComponent(syntheticLocationId)}`, + {}, + ); + } + async getDynatraceProblems( dynatraceEntityId: string, ): Promise { if (!dynatraceEntityId) { - throw new Error('Dynatrace entity ID is required'); + throw new Error('Dynatrace entity id is required'); } return this.callApi('problems', { diff --git a/plugins/dynatrace/src/components/DynatraceTab/DynatraceTab.tsx b/plugins/dynatrace/src/components/DynatraceTab/DynatraceTab.tsx index c284eaba0f..3f58eed9dc 100644 --- a/plugins/dynatrace/src/components/DynatraceTab/DynatraceTab.tsx +++ b/plugins/dynatrace/src/components/DynatraceTab/DynatraceTab.tsx @@ -18,14 +18,16 @@ import { Grid } from '@material-ui/core'; import { Page, Content, - ContentHeader, - SupportButton, MissingAnnotationEmptyState, } from '@backstage/core-components'; import { useEntity } from '@backstage/plugin-catalog-react'; import { ProblemsList } from '../Problems/ProblemsList'; +import { SyntheticsCard } from '../Synthetics/SyntheticsCard'; import { isDynatraceAvailable } from '../../plugin'; -import { DYNATRACE_ID_ANNOTATION } from '../../constants'; +import { + DYNATRACE_ID_ANNOTATION, + DYNATRACE_SYNTHETICS_ANNOTATION, +} from '../../constants'; export const DynatraceTab = () => { const { entity } = useEntity(); @@ -37,18 +39,30 @@ export const DynatraceTab = () => { const dynatraceEntityId: string = entity?.metadata.annotations?.[DYNATRACE_ID_ANNOTATION]!; + const syntheticsIds: string = + entity?.metadata.annotations?.[DYNATRACE_SYNTHETICS_ANNOTATION]!; + return ( - - - Plugin to show information from Dynatrace - - - - - + {dynatraceEntityId ? ( + + + + ) : ( + '' + )} + {syntheticsIds + ?.split(/[ ,]/) + .filter(Boolean) + .map(id => { + return ( + + + + ); + })} diff --git a/plugins/dynatrace/src/components/Problems/ProblemsList/ProblemsList.test.tsx b/plugins/dynatrace/src/components/Problems/ProblemsList/ProblemsList.test.tsx index 1249f4e886..9b9c51dca1 100644 --- a/plugins/dynatrace/src/components/Problems/ProblemsList/ProblemsList.test.tsx +++ b/plugins/dynatrace/src/components/Problems/ProblemsList/ProblemsList.test.tsx @@ -36,12 +36,12 @@ describe('ProblemStatus', () => { .mockResolvedValue({ problems }); const rendered = await renderInTestApp( - + , ); 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( @@ -49,7 +49,7 @@ describe('ProblemStatus', () => { , ); expect( - await rendered.findByText('Nothing to report :)'), + await rendered.findByText('No Problems to Report!'), ).toBeInTheDocument(); }); }); diff --git a/plugins/dynatrace/src/components/Problems/ProblemsList/ProblemsList.tsx b/plugins/dynatrace/src/components/Problems/ProblemsList/ProblemsList.tsx index ac69abf93e..2ee0dda48d 100644 --- a/plugins/dynatrace/src/components/Problems/ProblemsList/ProblemsList.tsx +++ b/plugins/dynatrace/src/components/Problems/ProblemsList/ProblemsList.tsx @@ -15,19 +15,40 @@ */ import React from 'react'; import useAsync from 'react-use/lib/useAsync'; -import { Progress } from '@backstage/core-components'; -import Alert from '@material-ui/lab/Alert'; -import { useApi } from '@backstage/core-plugin-api'; +import { + Progress, + ResponseErrorPanel, + EmptyState, +} from '@backstage/core-components'; +import { useApi, configApiRef } from '@backstage/core-plugin-api'; import { ProblemsTable } from '../ProblemsTable'; -import { dynatraceApiRef } from '../../../api'; +import { dynatraceApiRef, DynatraceProblem } from '../../../api'; +import { InfoCard } from '@backstage/core-components'; type ProblemsListProps = { dynatraceEntityId: string; }; +const cardContents = ( + problems: DynatraceProblem[], + dynatraceBaseUrl: string, +) => { + return problems.length ? ( + + ) : ( + + ); +}; + export const ProblemsList = (props: ProblemsListProps) => { const { dynatraceEntityId } = props; + const configApi = useApi(configApiRef); const dynatraceApi = useApi(dynatraceApiRef); + const dynatraceBaseUrl = configApi.getString('dynatrace.baseUrl'); + const { value, loading, error } = useAsync(async () => { return dynatraceApi.getDynatraceProblems(dynatraceEntityId); }, [dynatraceApi, dynatraceEntityId]); @@ -36,8 +57,18 @@ export const ProblemsList = (props: ProblemsListProps) => { if (loading) { return ; } else if (error) { - return {error.message}; + return ; } - if (!problems) return
Nothing to report :)
; - return ; + return ( + + {cardContents(problems || [], dynatraceBaseUrl)} + + ); }; diff --git a/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.test.tsx b/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.test.tsx index e4adc43656..aaa3ea0024 100644 --- a/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.test.tsx +++ b/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.test.tsx @@ -28,17 +28,9 @@ describe('ProblemsTable', () => { it('renders the table with some problem data', async () => { const rendered = await renderInTestApp( - , + , , ); - expect(await rendered.findByText('example-service')).toBeInTheDocument(); - }); - it('renders an empty table when no data is provided', async () => { - const rendered = await renderInTestApp( - - - , - ); - expect(await rendered.findByText('Problems')).toBeInTheDocument(); + expect(await rendered.findByTitle('Search')).toBeInTheDocument(); }); }); diff --git a/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.tsx b/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.tsx index 8e3d330c37..78d26e3fe2 100644 --- a/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.tsx +++ b/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.tsx @@ -17,17 +17,19 @@ import React from 'react'; import { Table, TableColumn } from '@backstage/core-components'; import { DynatraceProblem } from '../../../api/DynatraceApi'; import { ProblemStatus } from '../ProblemStatus'; -import { configApiRef } from '@backstage/core-plugin-api'; -import { useApi } from '@backstage/core-plugin-api'; import { Link } from '@material-ui/core'; type ProblemsTableProps = { problems: DynatraceProblem[]; + dynatraceBaseUrl: string; }; -export const ProblemsTable = ({ problems }: ProblemsTableProps) => { - const configApi = useApi(configApiRef); - const dynatraceBaseUrl = configApi.getString('dynatrace.baseUrl'); +const parseTimestamp = (timestamp: number | undefined) => { + return timestamp ? new Date(timestamp).toLocaleString() : 'N/A'; +}; + +export const ProblemsTable = (props: ProblemsTableProps) => { + const { problems, dynatraceBaseUrl } = props; const columns: TableColumn[] = [ { title: 'Title', @@ -62,20 +64,18 @@ export const ProblemsTable = ({ problems }: ProblemsTableProps) => { { title: 'Start Time', field: 'startTime', - render: (row: Partial) => - new Date(row.startTime || 0).toString(), + render: (row: Partial) => parseTimestamp(row.startTime), }, { title: 'End Time', field: 'endTime', render: (row: Partial) => - row.endTime === -1 ? 'ongoing' : new Date(row.endTime || 0).toString(), + row.endTime === -1 ? 'ongoing' : parseTimestamp(row.endTime), }, ]; return ( { diff --git a/plugins/dynatrace/src/components/Synthetics/SyntheticsCard/SyntheticsCard.test.tsx b/plugins/dynatrace/src/components/Synthetics/SyntheticsCard/SyntheticsCard.test.tsx new file mode 100644 index 0000000000..e9083a3f82 --- /dev/null +++ b/plugins/dynatrace/src/components/Synthetics/SyntheticsCard/SyntheticsCard.test.tsx @@ -0,0 +1,52 @@ +/* + * 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. + */ +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'; + +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( + + , + , + ); + expect( + await rendered.findByText('View this Synthetic in Dynatrace'), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/dynatrace/src/components/Synthetics/SyntheticsCard/SyntheticsCard.tsx b/plugins/dynatrace/src/components/Synthetics/SyntheticsCard/SyntheticsCard.tsx new file mode 100644 index 0000000000..6b51a1725c --- /dev/null +++ b/plugins/dynatrace/src/components/Synthetics/SyntheticsCard/SyntheticsCard.tsx @@ -0,0 +1,88 @@ +/* + * 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. + */ +import React from 'react'; +import useAsync from 'react-use/lib/useAsync'; +import { Progress, ResponseErrorPanel } from '@backstage/core-components'; +import { InfoCard } from '@backstage/core-components'; +import { useApi, configApiRef } from '@backstage/core-plugin-api'; +import { dynatraceApiRef } from '../../../api'; +import { SyntheticsLocation } from '../SyntheticsLocation'; + +type SyntheticsCardProps = { + syntheticsId: string; +}; + +const dynatraceMonitorPrefixes = (idPrefix: string): string => { + switch (idPrefix) { + case 'HTTP_CHECK': + return 'ui/http-monitor'; + case 'BROWSER_MONITOR': + return 'ui/browser-monitor'; + case 'SYNTHETIC_TEST': + return 'ui/browser-monitor'; + default: + throw new Error('Invalid synthetic Id'); + } +}; + +export const SyntheticsCard = (props: SyntheticsCardProps) => { + const { syntheticsId } = props; + const configApi = useApi(configApiRef); + const dynatraceApi = useApi(dynatraceApiRef); + const dynatraceBaseUrl = configApi.getString('dynatrace.baseUrl'); + + const { value, loading, error } = useAsync(async () => { + return dynatraceApi.getDynatraceSyntheticFailures(syntheticsId); + }, [dynatraceApi, syntheticsId]); + + if (loading) { + return ; + } else if (error) { + return ; + } + + const deepLinkPrefix = dynatraceMonitorPrefixes( + `${syntheticsId.split('-')[0]}`, + ); + + const lastFailed = value?.locationsExecutionResults.map(l => { + return { + timestamp: l.requestResults[0].startTimestamp, + location: Number(l.locationId).toString(16), + }; + }); + + return ( + + {lastFailed?.map(l => { + return ( + + ); + })} + + ); +}; diff --git a/plugins/dynatrace/src/components/Synthetics/SyntheticsCard/index.ts b/plugins/dynatrace/src/components/Synthetics/SyntheticsCard/index.ts new file mode 100644 index 0000000000..5284e0cfab --- /dev/null +++ b/plugins/dynatrace/src/components/Synthetics/SyntheticsCard/index.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ +export { SyntheticsCard } from './SyntheticsCard'; diff --git a/plugins/dynatrace/src/components/Synthetics/SyntheticsLocation/SyntheticsLocation.test.tsx b/plugins/dynatrace/src/components/Synthetics/SyntheticsLocation/SyntheticsLocation.test.tsx new file mode 100644 index 0000000000..de04f73e36 --- /dev/null +++ b/plugins/dynatrace/src/components/Synthetics/SyntheticsLocation/SyntheticsLocation.test.tsx @@ -0,0 +1,65 @@ +/* + * 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. + */ +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'; + +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( + + + , + , + ); + 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( + + + , + , + ); + expect(await rendered.findByText(/__location__/)).toBeInTheDocument(); + expect(await rendered.queryByText(/failed/)).not.toBeInTheDocument(); + }); +}); diff --git a/plugins/dynatrace/src/components/Synthetics/SyntheticsLocation/SyntheticsLocation.tsx b/plugins/dynatrace/src/components/Synthetics/SyntheticsLocation/SyntheticsLocation.tsx new file mode 100644 index 0000000000..4d328210cb --- /dev/null +++ b/plugins/dynatrace/src/components/Synthetics/SyntheticsLocation/SyntheticsLocation.tsx @@ -0,0 +1,75 @@ +/* + * 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. + */ + +import React from 'react'; +import useAsync from 'react-use/lib/useAsync'; +import { Progress, ResponseErrorPanel } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; +import { Chip } from '@material-ui/core'; +import { dynatraceApiRef } from '../../../api'; + +type SyntheticsLocationProps = { + lastFailedTimestamp: Date; + locationId: string; + key: string; +}; + +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 (failedInLastXHours(timestamp, 1)) { + return 'salmon'; + } + if (failedInLastXHours(timestamp, 6)) { + return 'sandybrown'; + } + if (failedInLastXHours(timestamp, 24)) { + return 'palegoldenrod'; + } + return 'lightgreen'; +}; + +export const SyntheticsLocation = (props: SyntheticsLocationProps) => { + const { lastFailedTimestamp, locationId } = props; + const dynatraceApi = useApi(dynatraceApiRef); + const { value, loading, error } = useAsync(async () => { + return dynatraceApi.getDynatraceSyntheticLocationInfo( + `SYNTHETIC_LOCATION-00000000000000${locationId}`, + ); + }); + + if (loading) { + return ; + } else if (error) { + return ; + } + + return ( + + ); +}; diff --git a/plugins/dynatrace/src/components/Synthetics/SyntheticsLocation/index.ts b/plugins/dynatrace/src/components/Synthetics/SyntheticsLocation/index.ts new file mode 100644 index 0000000000..1dc8a783fe --- /dev/null +++ b/plugins/dynatrace/src/components/Synthetics/SyntheticsLocation/index.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ +export { SyntheticsLocation } from './SyntheticsLocation'; diff --git a/plugins/dynatrace/src/constants.ts b/plugins/dynatrace/src/constants.ts index 21453e0338..c4cc48d48a 100644 --- a/plugins/dynatrace/src/constants.ts +++ b/plugins/dynatrace/src/constants.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export const DYNATRACE_ID_ANNOTATION = 'dynatrace.com/dynatrace-entity-id'; +export const DYNATRACE_SYNTHETICS_ANNOTATION = 'dynatrace.com/synthetics-ids'; diff --git a/plugins/dynatrace/src/mocks/problems.json b/plugins/dynatrace/src/mocks/problems.json index b1894bef56..16e3e156ec 100644 --- a/plugins/dynatrace/src/mocks/problems.json +++ b/plugins/dynatrace/src/mocks/problems.json @@ -1,5 +1,5 @@ { - "totalCount": 0, + "totalCount": 1, "pageSize": 0, "nextPageKey": "AQAAABQBAAAABQ==", "problems": [ diff --git a/plugins/dynatrace/src/plugin.ts b/plugins/dynatrace/src/plugin.ts index fc1778b45c..26ecb1aba1 100644 --- a/plugins/dynatrace/src/plugin.ts +++ b/plugins/dynatrace/src/plugin.ts @@ -23,7 +23,10 @@ import { } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; -import { DYNATRACE_ID_ANNOTATION } from './constants'; +import { + DYNATRACE_ID_ANNOTATION, + DYNATRACE_SYNTHETICS_ANNOTATION, +} from './constants'; import { rootRouteRef } from './routes'; @@ -55,7 +58,8 @@ export const dynatracePlugin = createPlugin({ * @param entity {Entity} - The entity to check for the dynatrace id annotation. */ export const isDynatraceAvailable = (entity: Entity) => - Boolean(entity.metadata.annotations?.[DYNATRACE_ID_ANNOTATION]); + Boolean(entity.metadata.annotations?.[DYNATRACE_ID_ANNOTATION]) || + Boolean(entity.metadata.annotations?.[DYNATRACE_SYNTHETICS_ANNOTATION]); /** * Creates a routable extension for the dynatrace plugin tab.