Merge pull request #16799 from mannkind/fireHydrantAnnotation

FireHydrant plugin annotation
This commit is contained in:
Fredrik Adelöw
2023-03-14 13:25:07 +01:00
committed by GitHub
13 changed files with 154 additions and 10 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-firehydrant': minor
---
Allow firehydrant to use component annotation
+8
View File
@@ -56,3 +56,11 @@ proxy:
# Supply the token you generated from https://app.firehydrant.io/organizations/bots
Authorization: Bearer fhb-e4911b22bcd788c4a4afeb0c111ffbfa
```
4. Optionally add an annotation to the yaml config file of a component
```yaml
metadata:
annotations:
firehydrant.com/service-name: <firehydrant-service-name>
```
+4
View File
@@ -6,6 +6,7 @@
/// <reference types="react" />
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { Entity } from '@backstage/catalog-model';
import { RouteRef } from '@backstage/core-plugin-api';
// @public (undocumented)
@@ -19,4 +20,7 @@ export const firehydrantPlugin: BackstagePlugin<
{},
{}
>;
// @public (undocumented)
export const isFireHydrantAvailable: (entity: Entity) => boolean;
```
+1
View File
@@ -23,6 +23,7 @@
"clean": "backstage-cli package clean"
},
"dependencies": {
"@backstage/catalog-model": "workspace:^",
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
+6 -3
View File
@@ -30,6 +30,7 @@ export interface FireHydrantAPI {
getServiceDetails(options: {
serviceName: string;
lookupByName: boolean;
}): Promise<ServiceDetailsResponse>;
getServiceIncidents(options: {
@@ -77,11 +78,13 @@ export class FireHydrantAPIClient implements FireHydrantAPI {
async getServiceDetails(options: {
serviceName: string;
lookupByName: boolean;
}): Promise<ServiceDetailsResponse> {
const queryOpt = options.lookupByName ? 'name' : 'query';
const query = new URLSearchParams();
query.set(queryOpt, options.serviceName);
const proxyUrl = await this.getApiUrl();
const response = await fetch(
`${proxyUrl}/services?query=${options.serviceName}`,
);
const response = await fetch(`${proxyUrl}/services?${query}`);
if (!response.ok) {
throw new Error(
@@ -30,7 +30,9 @@ const apis = TestApiRegistry.from([fireHydrantApiRef, mockFireHydrantApi]);
jest.mock('@backstage/plugin-catalog-react', () => ({
useEntity: () => {
return { entity: { metadata: { name: 'service-example' } } };
return {
entity: { kind: 'Component', metadata: { name: 'service-example' } },
};
},
}));
@@ -39,6 +39,7 @@ import {
ResponseErrorPanel,
} from '@backstage/core-components';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import { isFireHydrantAvailable, getFireHydrantServiceName } from '../hooks';
const useStyles = makeStyles(theme => ({
button: {
@@ -149,14 +150,14 @@ export const ServiceDetailsCard = () => {
const startDate = DateTime.now().minus({ days: 30 }).toUTC();
const endDate = DateTime.now().toUTC();
// The service name is provided by an annotation or a Backstage generated service name.
// The Backstage service name in FireHydrant is a unique formatted string
// that requires the entity's kind, name, and namespace.
const fireHydrantServiceName = `${entity?.kind}:${
entity?.metadata?.namespace ?? 'default'
}/${entity?.metadata?.name}`;
const fireHydrantServiceName = getFireHydrantServiceName(entity);
const { loading, value, error } = useServiceDetails({
serviceName: fireHydrantServiceName,
lookupByName: isFireHydrantAvailable(entity),
});
const activeIncidents: string[] = value?.service?.active_incidents ?? [];
@@ -0,0 +1,78 @@
/*
* Copyright 2021 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 { Entity } from '@backstage/catalog-model';
import { isFireHydrantAvailable, getFireHydrantServiceName } from './hooks';
describe('firehydrant-hooks-isFireHydrantAvailable', () => {
it('should find an annotation', () => {
const e: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
namespace: 'default',
name: 'test-fh-name',
annotations: {
'firehydrant.com/service-name': 'test-fh-name',
},
},
};
expect(isFireHydrantAvailable(e)).toEqual(true);
});
it('should not find an annotation', () => {
const e: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
namespace: 'default',
name: 'test-fh-name',
},
};
expect(isFireHydrantAvailable(e)).toEqual(false);
});
});
describe('firehydrant-hooks-getFireHydrantServiceName', () => {
it('should return annotation service name', () => {
const expected = 'test-fh-name';
const e: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
namespace: 'default',
name: 'test-fh-name',
annotations: {
'firehydrant.com/service-name': expected,
},
},
};
expect(getFireHydrantServiceName(e)).toEqual(expected);
});
it('should return generated service name', () => {
const expected = 'component:default/test-fh-name';
const e: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
namespace: 'default',
name: 'test-fh-name',
annotations: {},
},
};
expect(getFireHydrantServiceName(e)).toEqual(expected);
});
});
@@ -0,0 +1,25 @@
/*
* Copyright 2023 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 { Entity, stringifyEntityRef } from '@backstage/catalog-model';
export const FIREHYDRANT_SERVICE_NAME_ANNOTATION =
'firehydrant.com/service-name';
/** @public */
export const isFireHydrantAvailable = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[FIREHYDRANT_SERVICE_NAME_ANNOTATION]);
export const getFireHydrantServiceName = (entity: Entity) =>
entity?.metadata.annotations?.[FIREHYDRANT_SERVICE_NAME_ANNOTATION] ??
stringifyEntityRef(entity);
@@ -17,13 +17,22 @@ import useAsyncRetry from 'react-use/lib/useAsyncRetry';
import { fireHydrantApiRef } from '../api';
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
export const useServiceDetails = ({ serviceName }: { serviceName: string }) => {
export const useServiceDetails = ({
serviceName,
lookupByName,
}: {
serviceName: string;
lookupByName: boolean;
}) => {
const api = useApi(fireHydrantApiRef);
const errorApi = useApi(errorApiRef);
const { loading, value, error, retry } = useAsyncRetry(async () => {
try {
return await api.getServiceDetails({ serviceName: serviceName });
return await api.getServiceDetails({
serviceName: serviceName,
lookupByName: lookupByName,
});
} catch (e) {
errorApi.post(e);
return Promise.reject(e);
+5 -1
View File
@@ -20,4 +20,8 @@
* @packageDocumentation
*/
export { firehydrantPlugin, FirehydrantCard } from './plugin';
export {
firehydrantPlugin,
FirehydrantCard,
isFireHydrantAvailable,
} from './plugin';
+3
View File
@@ -50,3 +50,6 @@ export const FirehydrantCard = firehydrantPlugin.provide(
},
}),
);
/** @public */
export { isFireHydrantAvailable } from './components/hooks';
+1
View File
@@ -6249,6 +6249,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@backstage/plugin-firehydrant@workspace:plugins/firehydrant"
dependencies:
"@backstage/catalog-model": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/core-app-api": "workspace:^"
"@backstage/core-components": "workspace:^"