Merge branch 'backstage:master' into airbrake-initial

This commit is contained in:
Karan Shah
2021-12-30 10:28:07 +00:00
committed by GitHub
19 changed files with 293 additions and 56 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder': patch
---
Implement a `EntityTagsPicker` field extension
+2 -2
View File
@@ -1,5 +1,5 @@
---
'@backstage/plugin-tech-insights': minor
'@backstage/plugin-tech-insights': patch
---
fixes api auth in tech-insights plugin
Fixed API auth in tech-insights plugin
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Support custom file name for `catalog:write` action
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Filter out projects with missing `default_branch` from GitLab Discovery.
+1
View File
@@ -78,3 +78,4 @@
| [Keyloop](https://www.keyloop.com/) | [Andre Wanlin](https://github.com/awanlin) | Future-motive Developer Portal to help our teams create technology to make everything about buying and owning a car better. 🚗 |
| [Simply Business](https://sbtech.simplybusiness.co.uk/) | [@addersuk](https://github.com/addersuk), [@LightningStairs](https://github.com/LightningStairs), [@punitcse](https://github.com/punitcse), [@moltenice](https://github.com/moltenice) | Central developer portal to access everything a developer needs such as docs, internal service catalog, and the ability to quickly create a new service from a template. Internally developed Backstage plugins allow us to customise the experience to how we work. |
| [Overwolf](https://www.overwolf.com) | [@tomwolfgang](https://github.com/tomwolfgang) | Dev portal - software catalog, tech-docs, scaffolding |
| [Hotmart](https://www.hotmart.com) | [@fabioviana-hotmart](https://github.com/fabioviana-hotmart) | The main Developers Portal to centralize docs, applications and technical metrics. |
+61 -42
View File
@@ -53,8 +53,10 @@ To add a custom theme to your Backstage app, you pass it as configuration to
For example, adding the theme that we created in the previous section can be
done like this:
```ts
```tsx
import { createApp } from '@backstage/app-defaults';
import { ThemeProvider } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
const app = createApp({
apis: ...,
@@ -63,7 +65,11 @@ const app = createApp({
id: 'my-theme',
title: 'My Custom Theme',
variant: 'light',
theme: myTheme,
Provider: ({ children }) => (
<ThemeProvider theme={myTheme}>
<CssBaseline>{children}</CssBaseline>
</ThemeProvider>
),
}]
})
```
@@ -76,60 +82,67 @@ want to use the default themes, they are exported as `lightTheme` and
## Example of a custom theme
```ts
const themeOptions = createThemeOptions({
import {
createTheme,
genPageTheme,
lightTheme,
shapes,
} from '@backstage/theme';
const myTheme = createTheme({
palette: {
...lightTheme.palette,
primary: {
main: '#123456',
main: '#343b58',
},
secondary: {
main: '#123456',
main: '#565a6e',
},
error: {
main: '#123456',
main: '#8c4351',
},
warning: {
main: '#123456',
main: '#8f5e15',
},
info: {
main: '#123456',
main: '#34548a',
},
success: {
main: '#123456',
main: '#485e30',
},
background: {
default: '#123456',
paper: '#123456',
default: '#d5d6db',
paper: '#d5d6db',
},
banner: {
info: '#123456',
error: '#123456',
text: '#123456',
link: '#123456',
info: '#34548a',
error: '#8c4351',
text: '#343b58',
link: '#565a6e',
},
errorBackground: '#123456',
warningBackground: '#123456',
infoBackground: '#123456',
errorBackground: '#8c4351',
warningBackground: '#8f5e15',
infoBackground: '#343b58',
navigation: {
background: '#123456',
indicator: '#123456',
color: '#123456',
selectedColor: '#123456',
background: '#343b58',
indicator: '#8f5e15',
color: '#d5d6db',
selectedColor: '#ffffff',
},
},
defaultPageTheme: 'home',
fontFamily: 'Comic Sans',
fontFamily: 'Comic Sans MS',
/* below drives the header colors */
pageTheme: {
home: genPageTheme(['#123456', '#123456'], shapes.wave),
documentation: genPageTheme(['#123456', '#123456'], shapes.wave2),
tool: genPageTheme(['#123456', '#123456'], shapes.round),
service: genPageTheme(['#123456', '#123456'], shapes.wave),
website: genPageTheme(['#123456', '#123456'], shapes.wave),
library: genPageTheme(['#123456', '#123456'], shapes.wave),
other: genPageTheme(['#123456', '#123456'], shapes.wave),
app: genPageTheme(['#123456', '#123456'], shapes.wave),
apis: genPageTheme(['#123456', '#123456'], shapes.wave),
home: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
documentation: genPageTheme(['#8c4351', '#343b58'], shapes.wave2),
tool: genPageTheme(['#8c4351', '#343b58'], shapes.round),
service: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
website: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
library: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
other: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
app: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
apis: genPageTheme(['#8c4351', '#343b58'], shapes.wave),
},
});
```
@@ -162,7 +175,7 @@ wouldn't be enough to alter the `box-shadow` property or to add css rules that
aren't already defined like a margin. For these cases you should also create an
override.
```ts
```tsx
import { createApp } from '@backstage/core-app-api';
import { BackstageTheme, lightTheme } from '@backstage/theme';
/**
@@ -187,6 +200,16 @@ export const createCustomThemeOverrides = (
};
};
const customTheme: BackstageTheme = {
...lightTheme,
overrides: {
// These are the overrides that Backstage applies to `material-ui` components
...lightTheme.overrides,
// These are your custom overrides, either to `material-ui` or Backstage components.
...createCustomThemeOverrides(lightTheme),
},
};
const app = createApp({
apis: ...,
plugins: ...,
@@ -194,15 +217,11 @@ const app = createApp({
id: 'my-theme',
title: 'My Custom Theme',
variant: 'light',
theme: {
...lightTheme,
overrides: {
// These are the overrides that Backstage applies to `material-ui` components
...lightTheme.overrides,
// These are your custom overrides, either to `material-ui` or Backstage components.
...createCustomThemeOverrides(lightTheme),
},
},
Provider: ({ children }) => (
<ThemeProvider theme={customTheme}>
<CssBaseline>{children}</CssBaseline>
</ThemeProvider>
),
}]
});
```
@@ -184,6 +184,13 @@ describe('GitlabDiscoveryProcessor', () => {
last_activity_at: '2021-08-05T11:03:05.774Z',
web_url: 'https://gitlab.fake/3',
},
{
id: 4,
archived: false,
default_branch: undefined, // MISSING DEFAULT BRANCH
last_activity_at: '2021-08-05T11:03:05.774Z',
web_url: 'https://gitlab.fake/4',
},
],
};
default:
@@ -94,9 +94,16 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor {
};
for await (const project of projects) {
result.scanned++;
if (!project.archived) {
result.matches.push(project);
if (project.archived) {
continue;
}
if (branch === '*' && project.default_branch === undefined) {
continue;
}
result.matches.push(project);
}
for (const project of result.matches) {
@@ -16,7 +16,7 @@
export type GitLabProject = {
id: number;
default_branch: string;
default_branch?: string;
archived: boolean;
last_activity_at: string;
web_url: string;
@@ -69,4 +69,33 @@ describe('catalog:write', () => {
yaml.stringify(entity),
);
});
it('should support a custom filename', async () => {
const entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'n',
namespace: 'ns',
annotations: {
[ORIGIN_LOCATION_ANNOTATION]: 'url:https://example.com',
},
},
spec: {},
};
await action.handler({
...mockContext,
input: {
filePath: 'some-dir/entity-info.yaml',
entity,
},
});
expect(fsMock.writeFile).toHaveBeenCalledTimes(1);
expect(fsMock.writeFile).toHaveBeenCalledWith(
resolvePath(mockContext.workspacePath, 'some-dir/entity-info.yaml'),
yaml.stringify(entity),
);
});
});
@@ -21,13 +21,18 @@ import { Entity } from '@backstage/catalog-model';
import { resolveSafeChildPath } from '@backstage/backend-common';
export function createCatalogWriteAction() {
return createTemplateAction<{ name?: string; entity: Entity }>({
return createTemplateAction<{ filePath?: string; entity: Entity }>({
id: 'catalog:write',
description: 'Writes the catalog-info.yaml for your template',
schema: {
input: {
type: 'object',
properties: {
filePath: {
title: 'Catalog file path',
description: 'Defaults to catalog-info.yaml',
type: 'string',
},
entity: {
title: 'Entity info to write catalog-info.yaml',
description:
@@ -39,10 +44,11 @@ export function createCatalogWriteAction() {
},
async handler(ctx) {
ctx.logStream.write(`Writing catalog-info.yaml`);
const { entity } = ctx.input;
const { filePath, entity } = ctx.input;
const path = filePath ?? 'catalog-info.yaml';
await fs.writeFile(
resolveSafeChildPath(ctx.workspacePath, 'catalog-info.yaml'),
resolveSafeChildPath(ctx.workspacePath, path),
yaml.stringify(entity),
);
},
+10
View File
@@ -80,6 +80,16 @@ export const EntityPicker: ({
// @public (undocumented)
export const EntityPickerFieldExtension: () => null;
// @public
export const EntityTagsPicker: ({
formData,
onChange,
uiSchema,
}: FieldProps<string[]>) => JSX.Element;
// @public
export const EntityTagsPickerFieldExtension: () => null;
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "FavouriteTemplate" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -0,0 +1,107 @@
/*
* 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 React, { useState } from 'react';
import { useAsync, useEffectOnce } from 'react-use';
import { CatalogEntitiesRequest } from '@backstage/catalog-client';
import { Entity, makeValidator } from '@backstage/catalog-model';
import { useApi } from '@backstage/core-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { FormControl, TextField } from '@material-ui/core';
import { Autocomplete } from '@material-ui/lab';
import { FieldProps } from '@rjsf/core';
/**
* EntityTagsPicker
* @public
*/
export const EntityTagsPicker = ({
formData,
onChange,
uiSchema,
}: FieldProps<string[]>) => {
const catalogApi = useApi(catalogApiRef);
const [inputValue, setInputValue] = useState('');
const [inputError, setInputError] = useState(false);
const tagValidator = makeValidator().isValidTag;
const kinds = uiSchema['ui:options']?.kinds as string[];
const { loading, value: existingTags } = useAsync(async () => {
const tagsRequest: CatalogEntitiesRequest = { fields: ['metadata.tags'] };
if (kinds) {
tagsRequest.filter = { kind: kinds };
}
const entities = await catalogApi.getEntities(tagsRequest);
return [
...new Set(
entities.items
.flatMap((e: Entity) => e.metadata?.tags)
.filter(Boolean) as string[],
),
].sort();
});
const setTags = (_: React.ChangeEvent<{}>, values: string[] | null) => {
// Reset error state in case all tags were removed
let hasError = false;
let addDuplicate = false;
const currentTags = formData || [];
// If adding a new tag
if (values?.length && currentTags.length < values.length) {
const newTag = (values[values.length - 1] = values[values.length - 1]
.toLocaleLowerCase('en-US')
.trim());
hasError = !tagValidator(newTag);
addDuplicate = currentTags.indexOf(newTag) !== -1;
}
setInputError(hasError);
setInputValue(!hasError ? '' : inputValue);
if (!hasError && !addDuplicate) {
onChange(values || []);
}
};
// Initialize field to always return an array
useEffectOnce(() => onChange(formData || []));
return (
<FormControl margin="normal">
<Autocomplete
multiple
freeSolo
filterSelectedOptions
onChange={setTags}
value={formData || []}
inputValue={inputValue}
loading={loading}
options={existingTags || []}
ChipProps={{ size: 'small' }}
renderInput={params => (
<TextField
{...params}
label="Tags"
onChange={e => setInputValue(e.target.value)}
error={inputError}
helperText="Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters"
/>
)}
/>
</FormControl>
);
};
@@ -0,0 +1,16 @@
/*
* 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.
*/
export { EntityTagsPicker } from './EntityTagsPicker';
@@ -19,3 +19,4 @@ export * from './OwnerPicker';
export * from './RepoUrlPicker';
export * from './TextValuePicker';
export * from './OwnedEntityPicker';
export * from './EntityTagsPicker';
@@ -18,6 +18,7 @@ import {
EntityNamePicker,
entityNamePickerValidation,
} from '../components/fields/EntityNamePicker';
import { EntityTagsPicker } from '../components/fields/EntityTagsPicker';
import { OwnerPicker } from '../components/fields/OwnerPicker';
import {
repoPickerValidation,
@@ -36,6 +37,10 @@ export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS: FieldExtensionOptions[] = [
name: 'EntityNamePicker',
validation: entityNamePickerValidation,
},
{
component: EntityTagsPicker,
name: 'EntityTagsPicker',
},
{
component: RepoUrlPicker,
name: 'RepoUrlPicker',
+2
View File
@@ -30,6 +30,7 @@ export type { CustomFieldValidator, FieldExtensionOptions } from './extensions';
export {
EntityPickerFieldExtension,
EntityNamePickerFieldExtension,
EntityTagsPickerFieldExtension,
OwnerPickerFieldExtension,
OwnedEntityPickerFieldExtension,
RepoUrlPickerFieldExtension,
@@ -40,6 +41,7 @@ export {
export {
EntityNamePicker,
EntityPicker,
EntityTagsPicker,
OwnerPicker,
RepoUrlPicker,
TextValuePicker,
+12
View File
@@ -36,6 +36,7 @@ import {
identityApiRef,
} from '@backstage/core-plugin-api';
import { OwnedEntityPicker } from './components/fields/OwnedEntityPicker';
import { EntityTagsPicker } from './components/fields/EntityTagsPicker';
export const scaffolderPlugin = createPlugin({
id: 'scaffolder',
@@ -103,3 +104,14 @@ export const OwnedEntityPickerFieldExtension = scaffolderPlugin.provide(
name: 'OwnedEntityPicker',
}),
);
/**
* EntityTagsPickerFieldExtension
* @public
*/
export const EntityTagsPickerFieldExtension = scaffolderPlugin.provide(
createScaffolderFieldExtension({
component: EntityTagsPicker,
name: 'EntityTagsPicker',
}),
);
+6 -6
View File
@@ -7839,9 +7839,9 @@
integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==
"@types/jscodeshift@^0.11.0":
version "0.11.0"
resolved "https://registry.npmjs.org/@types/jscodeshift/-/jscodeshift-0.11.0.tgz#7224cf1a4d0383b4fb2694ffed52f57b45c3325b"
integrity sha512-OcJgr5GznWCEx2Oeg4eMUZYwssTHFj/tU8grrNCKdFQtAEAa0ezDiPHbCdSkyWrRSurXrYbNbHdhxbbB76pXNg==
version "0.11.3"
resolved "https://registry.npmjs.org/@types/jscodeshift/-/jscodeshift-0.11.3.tgz#8dcab24ced39dcab1c8ff3461b3d171aafee3d48"
integrity sha512-pM0JD9kWVDH9DQp5Y6td16924V3MwZHei8P3cTeuFhXpzpk0K+iWraBZz8wF61QkFs9fZeAQNX0q8SG0+TFm2w==
dependencies:
ast-types "^0.14.1"
recast "^0.20.3"
@@ -14864,9 +14864,9 @@ express-prom-bundle@^6.3.6:
url-value-parser "^2.0.0"
express-promise-router@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/express-promise-router/-/express-promise-router-4.1.0.tgz#79160e145c27610ba411bceb0552a36f11dbab4f"
integrity sha512-nvg0X1Rj8oajPPC+fG3t4e740aNmQZRZY6dRLbiiM56Dvd8213RJ4kaxhZVTdQLut+l4DZdfeJkyx2VENPMBdw==
version "4.1.1"
resolved "https://registry.npmjs.org/express-promise-router/-/express-promise-router-4.1.1.tgz#8fac102060b9bcc868f84d34fbb12fd8fa494291"
integrity sha512-Lkvcy/ZGrBhzkl3y7uYBHLMtLI4D6XQ2kiFg9dq7fbktBch5gjqJ0+KovX0cvCAvTJw92raWunRLM/OM+5l4fA==
dependencies:
is-promise "^4.0.0"
lodash.flattendeep "^4.0.0"