implement createApiExtension

Co-authored-by: Patrik Oldsberg <poldsberg@gmail.com>
Co-authored-by: Johan Haals <johan.haals@gmail.com>
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2023-08-31 13:35:25 +02:00
parent 56c4216dbf
commit 09696af4e4
7 changed files with 193 additions and 0 deletions
@@ -5,6 +5,8 @@
```ts
/// <reference types="react" />
import { AnyApiFactory } from '@backstage/core-plugin-api';
import { AnyApiRef } from '@backstage/core-plugin-api';
import { ComponentType } from 'react';
import { JsonObject } from '@backstage/types';
import { z } from 'zod';
@@ -28,8 +30,40 @@ export interface BackstagePlugin {
export const coreExtensionData: {
reactComponent: ExtensionDataRef<ComponentType<{}>>;
routePath: ExtensionDataRef<string>;
apiFactory: ExtensionDataRef<AnyApiFactory>;
};
// @public (undocumented)
export function createApiExtension<
TConfig extends {},
TInputs extends Record<
string,
{
extensionData: AnyExtensionDataMap;
}
>,
>(
options: (
| {
api: AnyApiRef;
factory: (options: {
config: TConfig;
inputs: {
[pointName in keyof TInputs]: ExtensionDataValue<
TInputs[pointName]['extensionData']
>[];
};
}) => AnyApiFactory;
}
| {
factory: AnyApiFactory;
}
) & {
configSchema?: PortableSchema<TConfig>;
inputs?: TInputs;
},
): Extension<TConfig>;
// @public (undocumented)
export function createExtension<
TData extends AnyExtensionDataMap,
@@ -35,6 +35,7 @@
"react": "^16.13.1 || ^17.0.0"
},
"dependencies": {
"@backstage/core-plugin-api": "workspace:^",
"@backstage/types": "workspace:^",
"lodash": "^4.17.21",
"zod": "^3.21.4",
@@ -0,0 +1,82 @@
/*
* 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 { createApiExtension } from './createApiExtension';
import { createApiFactory, createApiRef } from '@backstage/core-plugin-api';
describe('createApiExtension', () => {
it('fills in the expected values for an existing factory', () => {
const api = createApiRef<{ foo: string }>({ id: 'test' });
const factory = createApiFactory({
api,
deps: {},
factory: () => ({ foo: 'bar' }),
});
const extension = createApiExtension({
factory,
});
expect(extension).toEqual({
$$type: 'extension',
id: 'apis.test',
at: 'core/apis',
disabled: false,
configSchema: undefined,
inputs: {},
output: {
api: {
$$type: 'extension-data',
id: 'core.api.factory',
},
},
factory: expect.any(Function),
});
});
it('fills in the expected values for a ref and custom factory', () => {
const api = createApiRef<{ foo: string }>({ id: 'test' });
const factory = jest.fn(() => ({ foo: 'bar' }));
const extension = createApiExtension({
api,
inputs: {},
factory({ config: _config, inputs: _inputs }) {
return createApiFactory({
api,
deps: {},
factory,
});
},
});
expect(extension).toEqual({
$$type: 'extension',
id: 'apis.test',
at: 'core/apis',
disabled: false,
configSchema: undefined,
inputs: {},
output: {
api: {
$$type: 'extension-data',
id: 'core.api.factory',
},
},
factory: expect.any(Function),
});
});
});
@@ -0,0 +1,72 @@
/*
* 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 { AnyApiFactory, AnyApiRef } from '@backstage/core-plugin-api';
import { PortableSchema } from '../createSchemaFromZod';
import {
AnyExtensionDataMap,
coreExtensionData,
createExtension,
ExtensionDataValue,
} from '../types';
/** @public */
export function createApiExtension<
TConfig extends {},
TInputs extends Record<string, { extensionData: AnyExtensionDataMap }>,
>(
options: (
| {
api: AnyApiRef;
factory: (options: {
config: TConfig;
inputs: {
[pointName in keyof TInputs]: ExtensionDataValue<
TInputs[pointName]['extensionData']
>[];
};
}) => AnyApiFactory;
}
| {
factory: AnyApiFactory;
}
) & {
configSchema?: PortableSchema<TConfig>;
inputs?: TInputs;
},
) {
const { factory, configSchema, inputs: extensionInputs } = options;
const apiRef =
'api' in options ? options.api : (factory as { api: AnyApiRef }).api;
return createExtension({
id: `apis.${apiRef.id}`,
at: 'core/apis',
inputs: extensionInputs,
configSchema,
output: {
api: coreExtensionData.apiFactory,
},
factory({ bind, config, inputs }) {
if (typeof factory === 'function') {
bind.api(factory({ config, inputs }));
} else {
bind.api(factory);
}
},
});
}
@@ -14,4 +14,5 @@
* limitations under the License.
*/
export { createApiExtension } from './createApiExtension';
export { createPageExtension } from './createPageExtension';
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { AnyApiFactory } from '@backstage/core-plugin-api';
import { ComponentType } from 'react';
import { PortableSchema } from './createSchemaFromZod';
@@ -34,6 +35,7 @@ export function createExtensionDataRef<T>(id: string): ExtensionDataRef<T> {
export const coreExtensionData = {
reactComponent: createExtensionDataRef<ComponentType>('core.reactComponent'),
routePath: createExtensionDataRef<string>('core.routing.path'),
apiFactory: createExtensionDataRef<AnyApiFactory>('core.api.factory'),
};
/** @public */