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/src/api/DynatraceApi.ts b/plugins/dynatrace/src/api/DynatraceApi.ts index 1865b20363..8993860e61 100644 --- a/plugins/dynatrace/src/api/DynatraceApi.ts +++ b/plugins/dynatrace/src/api/DynatraceApi.ts @@ -35,8 +35,25 @@ export type DynatraceProblem = { affectedEntities: Array; }; +type SyntheticRequestResults = { + startTimestamp: number; +}; + +export type DynatraceSyntheticResults = { + monitorId: string; + locationsExecutionResults: [ + { + locationId: number; + executionId: string; + requestResults: Array; + }, + ]; +}; + export interface DynatraceProblems { problems: Array; + totalCount: number; + pageSize: number; } export const dynatraceApiRef = createApiRef({ @@ -47,4 +64,7 @@ export type DynatraceApi = { getDynatraceProblems( dynatraceEntityId: string, ): Promise; + getDynatraceSyntheticFailures( + syntheticId: string, + ): Promise; }; diff --git a/plugins/dynatrace/src/api/DynatraceClient.ts b/plugins/dynatrace/src/api/DynatraceClient.ts index 27476ddbac..728f94de7b 100644 --- a/plugins/dynatrace/src/api/DynatraceClient.ts +++ b/plugins/dynatrace/src/api/DynatraceClient.ts @@ -13,7 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { DynatraceProblems, DynatraceApi } from './DynatraceApi'; +import { + DynatraceProblems, + DynatraceApi, + DynatraceSyntheticResults, +} from './DynatraceApi'; import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; export class DynatraceClient implements DynatraceApi { @@ -52,11 +56,21 @@ export class DynatraceClient implements DynatraceApi { ); } + async getDynatraceSyntheticFailures( + syntheticId: string, + ): Promise { + if (!syntheticId) { + throw new Error('Dynatrace synthetic Id is required'); + } + + return this.callApi(`synthetic/execution/${syntheticId}/FAILED`, {}); + } + 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/assets/emptystate.svg b/plugins/dynatrace/src/assets/emptystate.svg new file mode 100644 index 0000000000..8a0490727f --- /dev/null +++ b/plugins/dynatrace/src/assets/emptystate.svg @@ -0,0 +1 @@ + diff --git a/plugins/dynatrace/src/components/DynatraceTab/DynatraceTab.tsx b/plugins/dynatrace/src/components/DynatraceTab/DynatraceTab.tsx index c284eaba0f..071fc5d256 100644 --- a/plugins/dynatrace/src/components/DynatraceTab/DynatraceTab.tsx +++ b/plugins/dynatrace/src/components/DynatraceTab/DynatraceTab.tsx @@ -18,18 +18,24 @@ import { Grid } from '@material-ui/core'; import { Page, Content, - ContentHeader, - SupportButton, MissingAnnotationEmptyState, } from '@backstage/core-components'; import { useEntity } from '@backstage/plugin-catalog-react'; +import { useApi, configApiRef } from '@backstage/core-plugin-api'; 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(); + const configApi = useApi(configApiRef); + const dynatraceBaseUrl = configApi.getString('dynatrace.baseUrl'); + if (!isDynatraceAvailable(entity)) { return ; } @@ -37,18 +43,29 @@ 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 - - - + + {syntheticsIds ? ( + + + + ) : ( + <> + )} diff --git a/plugins/dynatrace/src/components/EmptyState/EmptyState.test.tsx b/plugins/dynatrace/src/components/EmptyState/EmptyState.test.tsx new file mode 100644 index 0000000000..b61d59e88d --- /dev/null +++ b/plugins/dynatrace/src/components/EmptyState/EmptyState.test.tsx @@ -0,0 +1,15 @@ +/* + * 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. + */ 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..89f10c5750 --- /dev/null +++ b/plugins/dynatrace/src/components/Synthetics/SyntheticsCard/SyntheticsCard.test.tsx @@ -0,0 +1,18 @@ +/* + * 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'; + +describe('SyntheticsCard', () => {}); 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..491a752b09 --- /dev/null +++ b/plugins/dynatrace/src/components/Synthetics/SyntheticsCard/SyntheticsCard.tsx @@ -0,0 +1,85 @@ +/* + * 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 } from '@backstage/core-components'; +import Alert from '@material-ui/lab/Alert'; +import { InfoCard } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; +import { Typography } from '@material-ui/core'; +import { dynatraceApiRef } from '../../../api'; + +type SyntheticsCardProps = { + syntheticsIds: string; + dynatraceBaseUrl: 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 { syntheticsIds, dynatraceBaseUrl } = props; + const dynatraceApi = useApi(dynatraceApiRef); + const { value, loading, error } = useAsync(async () => { + return dynatraceApi.getDynatraceSyntheticFailures(syntheticsIds); + }, [dynatraceApi, syntheticsIds]); + + if (loading) { + return ; + } else if (error) { + return {error.message}; + } + + const deepLinkPrefix = dynatraceMonitorPrefixes( + `${syntheticsIds.match(/(.+)-/)![1]}`, + ); + const timestamps = value?.locationsExecutionResults.map(l => { + return l.requestResults[0].startTimestamp; + }); + + return ( + + + Locations: {JSON.stringify(value?.locationsExecutionResults.length)} + + + Last Failures:{' '} + {JSON.stringify( + timestamps?.map(t => { + return new Date(t).toString(); + }), + )} + + + ); +}; 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/constants.ts b/plugins/dynatrace/src/constants.ts index 21453e0338..8f504db1f4 100644 --- a/plugins/dynatrace/src/constants.ts +++ b/plugins/dynatrace/src/constants.ts @@ -14,3 +14,5 @@ * limitations under the License. */ export const DYNATRACE_ID_ANNOTATION = 'dynatrace.com/dynatrace-entity-id'; +export const DYNATRACE_SYNTHETICS_ANNOTATION = + 'dynatrace.com/dynatrace-synthetics-ids';