Merge branch 'master' into storybook8

This commit is contained in:
Charles de Dreuille
2024-10-18 16:48:25 +01:00
9 changed files with 112 additions and 14 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-api-docs': patch
---
Uses theme values to style the API definition schema so that theme overrides apply.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Align with type declaration of template filter/global function by supporting undefined as return value.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/catalog-client': patch
---
Fix for certain filter fields in the `catalogApiMock` being case sensitive.
+2 -2
View File
@@ -354,7 +354,7 @@ Passport-supported authentication method.
## Custom ScmAuthApi Implementation
The default `ScmAuthAPi` provides integrations for `github`, `gitlab`, `azure` and `bitbucket` and is created by the following code in `packages/app/src/apis.ts`:
The default `ScmAuthApi` provides integrations for `github`, `gitlab`, `azure` and `bitbucket` and is created by the following code in `packages/app/src/apis.ts`:
```ts
ScmAuth.createDefaultApiFactory();
@@ -379,7 +379,7 @@ export const apis: AnyApiFactory[] = [
];
```
Then replace it with something like this, which will create an `ApiFactory` with only a github provider.
Then replace it with something like this, which will create an `ApiFactory` with only a GitHub provider.
```ts title="packages/app/src/apis.ts"
export const apis: AnyApiFactory[] = [
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { CATALOG_FILTER_EXISTS } from '../types';
import { InMemoryCatalogClient } from './InMemoryCatalogClient';
import { Entity } from '@backstage/catalog-model';
@@ -25,6 +26,7 @@ const entity1: Entity = {
name: 'e1',
uid: 'u1',
},
relations: [{ type: 'relatedTo', targetRef: 'customkind:default/e2' }],
};
const entity2: Entity = {
@@ -42,10 +44,32 @@ const entities = [entity1, entity2];
describe('InMemoryCatalogClient', () => {
it('getEntities', async () => {
const client = new InMemoryCatalogClient({ entities });
await expect(client.getEntities()).resolves.toEqual({ items: entities });
await expect(
client.getEntities({ filter: { 'metadata.name': 'E1' } }),
).resolves.toEqual({ items: [entity1] });
await expect(
client.getEntities({ filter: { 'metadata.uid': 'u2' } }),
).resolves.toEqual({ items: [entity2] });
await expect(
client.getEntities({ filter: { 'metadata.uid': 'U2' } }),
).resolves.toEqual({ items: [entity2] });
await expect(
client.getEntities({
filter: { 'relations.relatedto': CATALOG_FILTER_EXISTS },
}),
).resolves.toEqual({ items: [entity1] });
await expect(
client.getEntities({
filter: { 'relations.relatedTo': 'customkind:default/e2' },
}),
).resolves.toEqual({ items: [entity1] });
});
it('getEntitiesByRefs', async () => {
@@ -48,13 +48,22 @@ function buildEntitySearch(entity: Entity) {
const rows = traverse(entity);
if (entity.metadata?.name) {
rows.push({ key: 'metadata.name', value: entity.metadata.name });
rows.push({
key: 'metadata.name',
value: entity.metadata.name.toLocaleLowerCase('en-US'),
});
}
if (entity.metadata?.namespace) {
rows.push({ key: 'metadata.namespace', value: entity.metadata.namespace });
rows.push({
key: 'metadata.namespace',
value: entity.metadata.namespace.toLocaleLowerCase('en-US'),
});
}
if (entity.metadata?.uid) {
rows.push({ key: 'metadata.uid', value: entity.metadata.uid });
rows.push({
key: 'metadata.uid',
value: entity.metadata.uid.toLocaleLowerCase('en-US'),
});
}
if (!entity.metadata.namespace) {
@@ -64,8 +73,8 @@ function buildEntitySearch(entity: Entity) {
// Visit relations
for (const relation of entity.relations ?? []) {
rows.push({
key: `relations.${relation.type}`,
value: relation.targetRef,
key: `relations.${relation.type.toLocaleLowerCase('en-US')}`,
value: relation.targetRef.toLocaleLowerCase('en-US'),
});
}
@@ -94,7 +94,8 @@ const useStyles = makeStyles(theme => ({
.errors small`]: {
color: theme.palette.text.secondary,
},
[`& .parameter__name.required:after`]: {
[`& .parameter__name.required:after,
.parameter__name.required span`]: {
color: theme.palette.warning.dark,
},
[`& table.model,
@@ -132,6 +133,46 @@ const useStyles = makeStyles(theme => ({
[`& span.prop-type`]: {
color: theme.palette.success.light,
},
[`& .opblock-control-arrow svg, .authorization__btn .unlocked`]: {
fill: theme.palette.text.primary,
},
[`& .json-schema-2020-12__title,
.json-schema-2020-12-keyword__name,
.json-schema-2020-12-property .json-schema-2020-12__title,
.json-schema-2020-12-keyword--description`]: {
color: theme.palette.text.primary,
},
[`.json-schema-2020-12-accordion__icon svg`]: {
fill: theme.palette.text.primary,
},
[`& .json-schema-2020-12-accordion,
.json-schema-2020-12-expand-deep-button`]: {
background: 'none',
appearance: 'none',
},
[`& .json-schema-2020-12-expand-deep-button,
.json-schema-2020-12-keyword__name--secondary,
.json-schema-2020-12-keyword__value--secondary,
.json-schema-2020-12__attribute--muted,
.json-schema-2020-12-keyword__value--const,
.json-schema-2020-12-keyword__value--warning`]: {
color: theme.palette.text.secondary,
},
[`& .json-schema-2020-12-body,
.json-schema-2020-12-keyword__value--const,
.json-schema-2020-12-keyword__value--warning`]: {
borderColor: theme.palette.text.secondary,
},
[`.json-schema-2020-12__constraint--string`]: {
backgroundColor: theme.palette.primary.main,
},
[`& .json-schema-2020-12__attribute--primary`]: {
color: theme.palette.primary.main,
},
[`& .json-schema-2020-12-property--required>.json-schema-2020-12:first-of-type>.json-schema-2020-12-head .json-schema-2020-12__title:after`]:
{
color: theme.palette.warning.dark,
},
},
},
}));
@@ -107,8 +107,9 @@ describe('SecureTemplater', () => {
const mockFilter1 = jest.fn(() => 'filtered text');
const mockFilter2 = jest.fn((var1, var2) => `${var1} ${var2}`);
const mockFilter3 = jest.fn((var1, var2) => ({ var1, var2 }));
const mockFilter4 = jest.fn(() => undefined);
const renderWith = await SecureTemplater.loadRenderer({
templateFilters: { mockFilter1, mockFilter2, mockFilter3 },
templateFilters: { mockFilter1, mockFilter2, mockFilter3, mockFilter4 },
});
const renderWithout = await SecureTemplater.loadRenderer();
@@ -131,6 +132,7 @@ describe('SecureTemplater', () => {
var2: 'another extra arg',
}),
);
expect(renderWith('${{ inputValue | mockFilter4 }}', ctx)).toBe('');
expect(() => renderWithout('${{ inputValue | mockFilter1 }}', ctx)).toThrow(
/Error: filter not found: mockFilter1/,
@@ -152,8 +154,9 @@ describe('SecureTemplater', () => {
const mockGlobal1 = jest.fn(() => 'awesome global function');
const mockGlobal2 = 'foo';
const mockGlobal3 = 123456;
const mockGlobal4 = jest.fn(() => undefined);
const renderWith = await SecureTemplater.loadRenderer({
templateGlobals: { mockGlobal1, mockGlobal2, mockGlobal3 },
templateGlobals: { mockGlobal1, mockGlobal2, mockGlobal3, mockGlobal4 },
});
const renderWithout = await SecureTemplater.loadRenderer();
@@ -164,6 +167,7 @@ describe('SecureTemplater', () => {
);
expect(renderWith('${{ mockGlobal2 }}', ctx)).toBe('foo');
expect(renderWith('${{ mockGlobal3 }}', ctx)).toBe('123456');
expect(renderWith('${{ mockGlobal4() }}', ctx)).toBe('');
expect(() => renderWithout('${{ mockGlobal1() }}', ctx)).toThrow(
/Error: Unable to call `mockGlobal1`/,
@@ -52,14 +52,17 @@ const { render, renderCompat } = (() => {
});
compatEnv.addFilter('jsonify', compatEnv.getFilter('dump'));
const handleFunctionResult = (value) => {
return value === '' ? undefined : JSON.parse(value);
};
for (const name of JSON.parse(availableTemplateFilters)) {
env.addFilter(name, (...args) => JSON.parse(callFilter(name, args)));
env.addFilter(name, (...args) => handleFunctionResult(callFilter(name, args)));
}
for (const [name, value] of Object.entries(JSON.parse(availableTemplateGlobals))) {
env.addGlobal(name, value);
}
for (const name of JSON.parse(availableTemplateCallbacks)) {
env.addGlobal(name, (...args) => JSON.parse(callGlobal(name, args)));
env.addGlobal(name, (...args) => handleFunctionResult(callGlobal(name, args)));
}
let uninstallCompat = undefined;
@@ -187,7 +190,8 @@ export class SecureTemplater {
if (!Object.hasOwn(templateFilters, filterName)) {
return '';
}
return JSON.stringify(templateFilters[filterName](...args));
const rz = templateFilters[filterName](...args);
return rz === undefined ? '' : JSON.stringify(rz);
},
);
@@ -201,7 +205,8 @@ export class SecureTemplater {
if (typeof global !== 'function') {
return '';
}
return JSON.stringify(global(...args));
const rz = global(...args);
return rz === undefined ? '' : JSON.stringify(rz);
},
);