Merge branch 'master' of https://github.com/backstage/backstage into bazaar-columns
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-kubernetes': patch
|
||||
---
|
||||
|
||||
fix: kubernetes plugin shall pass id token on get clusters request if possible
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-msgraph': patch
|
||||
---
|
||||
|
||||
Tweak logic for msgraph catalog ingesting for display names with security groups
|
||||
|
||||
Previously security groups that weren't mail enabled were imported with UUIDs, now they use the display name.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Allow for `cellStyle` property on `TableColumn` to be a function as well as `React.CSSProperties` as per the Material UI Table component
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/plugin-azure-devops': patch
|
||||
'@backstage/plugin-azure-devops-backend': minor
|
||||
'@backstage/plugin-azure-devops-common': patch
|
||||
---
|
||||
|
||||
refactor(`@backstage/plugin-azure-devops`): Consume types from `@backstage/plugin-azure-devops-common`.
|
||||
Stop re-exporting types from `@backstage/plugin-azure-devops-backend`.
|
||||
Added new types to `@backstage/plugin-azure-devops-common`.
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
'@backstage/techdocs-common': patch
|
||||
---
|
||||
|
||||
1. Techdocs publisher constructors now use parameter objects when being
|
||||
instantiated
|
||||
|
||||
2. Internal refactor of `LocalPublish` publisher to use `fromConfig` for
|
||||
creation to be aligned with other publishers; this does not impact
|
||||
`LocalPublish` usage.
|
||||
|
||||
```diff
|
||||
- const publisher = new LocalPublish(config, logger, discovery);
|
||||
+ const publisher = LocalPublish.fromConfig(config, logger, discovery);
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Only use settings that have a value when creating a new FirestoreKeyStore instance
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Added forwarding of the `audience` option for the SAML provider, making it possible to enable `audience` verification.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Removed the version pinning of the packages `graphql-language-service-interface` and `graphql-language-service-parser`. This should no longer be necessary.
|
||||
|
||||
You can apply the same change in your repository by ensuring that the following does _NOT_ appear in your root `package.json`.
|
||||
|
||||
```json
|
||||
"resolutions": {
|
||||
"graphql-language-service-interface": "2.8.2",
|
||||
"graphql-language-service-parser": "1.9.0"
|
||||
},
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Bumped `@spotify/eslint-config-typescript` from `v10` to `v12`, dropping support for Node.js v12.
|
||||
@@ -64,3 +64,4 @@
|
||||
| [SoundCloud](https://www.soundcloud.com) | [Julio Zynger](https://github.com/julioz) | Developer portal as a [humane registry](https://martinfowler.com/bliki/HumaneRegistry.html) for the organization: catalog of people, services, documentation, feature toggles, escalation policies, etc. |
|
||||
| [Volvofinans Bank](https://www.volvofinans.se) | [Johan Hammar](https://github.com/johanhammar) | Developer portal enabling engineers to manage and explore software and documentation. |
|
||||
| [Palo Alto Networks](https://www.paloaltonetworks.com) | [Jeremy Guarini](https://github.com/jeremyguarini), [Brian Lomeland](https://github.com/bbbmmmlll), [Palo Alto Networks](https://github.com/PaloAltoNetworks) | Developer portal, service catalog, documentation and tooling |
|
||||
| [Signal Iduna Group](https://www.signal-iduna.de/) | [Jonas Thomsen](https://github.com/JoThomsen) | Developer Portal, documentation, monitoring, service catalog for our insurance ecosystem
|
||||
|
||||
+1
-3
@@ -46,9 +46,7 @@
|
||||
"resolutions": {
|
||||
"**/@graphql-codegen/cli/**/ws": "^7.4.6",
|
||||
"**/@roadiehq/**/@backstage/plugin-catalog": "*",
|
||||
"**/@roadiehq/**/@backstage/catalog-model": "*",
|
||||
"graphql-language-service-interface": "2.8.2",
|
||||
"graphql-language-service-parser": "1.9.0"
|
||||
"**/@roadiehq/**/@backstage/catalog-model": "*"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"@rollup/plugin-yaml": "^3.0.0",
|
||||
"@spotify/eslint-config-base": "^12.0.0",
|
||||
"@spotify/eslint-config-react": "^10.0.0",
|
||||
"@spotify/eslint-config-typescript": "^10.0.0",
|
||||
"@spotify/eslint-config-typescript": "^12.0.0",
|
||||
"@sucrase/jest-plugin": "^2.1.1",
|
||||
"@sucrase/webpack-loader": "^2.0.0",
|
||||
"@svgr/plugin-jsx": "5.5.x",
|
||||
|
||||
@@ -326,3 +326,49 @@ export const FilterTable = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const StyledTable = () => {
|
||||
const classes = useStyles();
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
title: 'Column 1',
|
||||
field: 'col1',
|
||||
highlight: true,
|
||||
cellStyle: (_, rowData: any & { tableData: { id: number } }) => {
|
||||
return rowData.tableData.id % 2 === 0
|
||||
? {
|
||||
color: '#6CD75F',
|
||||
}
|
||||
: {
|
||||
color: '#DC3D5A',
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Column 2',
|
||||
field: 'col2',
|
||||
cellStyle: { color: '#2FA5DC' },
|
||||
},
|
||||
{
|
||||
title: 'Numeric value',
|
||||
field: 'number',
|
||||
type: 'numeric',
|
||||
},
|
||||
{
|
||||
title: 'A Date',
|
||||
field: 'date',
|
||||
type: 'date',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<Table
|
||||
options={{ paging: false }}
|
||||
data={testData10}
|
||||
columns={columns}
|
||||
title="Backstage Table"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,17 +18,18 @@ import React from 'react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { Table } from './Table';
|
||||
|
||||
const column1 = {
|
||||
title: 'Column 1',
|
||||
field: 'col1',
|
||||
};
|
||||
|
||||
const column2 = {
|
||||
title: 'Column 2',
|
||||
field: 'col2',
|
||||
};
|
||||
|
||||
const minProps = {
|
||||
columns: [
|
||||
{
|
||||
title: 'Column 1',
|
||||
field: 'col1',
|
||||
},
|
||||
{
|
||||
title: 'Column 2',
|
||||
field: 'col2',
|
||||
},
|
||||
],
|
||||
columns: [column1, column2],
|
||||
data: [
|
||||
{
|
||||
col1: 'first value, first row',
|
||||
@@ -47,6 +48,100 @@ describe('<Table />', () => {
|
||||
expect(rendered.getByText('second value, second row')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('with style rows', () => {
|
||||
describe('with CSS Properties object', () => {
|
||||
const styledColumn2 = {
|
||||
...column2,
|
||||
cellStyle: {
|
||||
color: 'blue',
|
||||
},
|
||||
};
|
||||
|
||||
it('renders non-highlighted correctly', async () => {
|
||||
const columns = [column1, styledColumn2];
|
||||
|
||||
const rendered = await renderInTestApp(
|
||||
<Table data={minProps.data} columns={columns} />,
|
||||
);
|
||||
expect(rendered.getByText('second value, first row')).toHaveStyle({
|
||||
color: 'blue',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders highlighted column correctly', async () => {
|
||||
const columns = [
|
||||
column1,
|
||||
{
|
||||
...styledColumn2,
|
||||
highlight: true,
|
||||
},
|
||||
];
|
||||
|
||||
const rendered = await renderInTestApp(
|
||||
<Table data={minProps.data} columns={columns} />,
|
||||
);
|
||||
expect(rendered.getByText('second value, first row')).toHaveStyle({
|
||||
color: 'blue',
|
||||
'font-weight': 700,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with CSS Properties function', () => {
|
||||
const styledColumn2 = {
|
||||
...column2,
|
||||
cellStyle: (
|
||||
_data: any,
|
||||
rowData: any & { tableData: { id: number } },
|
||||
) => {
|
||||
return rowData.tableData.id % 2 === 0
|
||||
? {
|
||||
color: 'green',
|
||||
}
|
||||
: {
|
||||
color: 'red',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
it('renders non-highlighted columns correctly', async () => {
|
||||
const columns = [column1, styledColumn2];
|
||||
|
||||
const rendered = await renderInTestApp(
|
||||
<Table data={minProps.data} columns={columns} />,
|
||||
);
|
||||
expect(rendered.getByText('second value, first row')).toHaveStyle({
|
||||
color: 'green',
|
||||
});
|
||||
expect(rendered.getByText('second value, second row')).toHaveStyle({
|
||||
color: 'red',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders highlighted columns correctly', async () => {
|
||||
const columns = [
|
||||
column1,
|
||||
{
|
||||
...styledColumn2,
|
||||
highlight: true,
|
||||
},
|
||||
];
|
||||
|
||||
const rendered = await renderInTestApp(
|
||||
<Table data={minProps.data} columns={columns} />,
|
||||
);
|
||||
expect(rendered.getByText('second value, first row')).toHaveStyle({
|
||||
color: 'green',
|
||||
'font-weight': 700,
|
||||
});
|
||||
expect(rendered.getByText('second value, second row')).toHaveStyle({
|
||||
color: 'red',
|
||||
'font-weight': 700,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders with subtitle', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<Table subtitle="subtitle" {...minProps} />,
|
||||
|
||||
@@ -169,12 +169,26 @@ function convertColumns<T extends object>(
|
||||
): TableColumn<T>[] {
|
||||
return columns.map(column => {
|
||||
const headerStyle: React.CSSProperties = {};
|
||||
const cellStyle: React.CSSProperties =
|
||||
typeof column.cellStyle === 'object' ? column.cellStyle : {};
|
||||
|
||||
let cellStyle = column.cellStyle || {};
|
||||
|
||||
if (column.highlight) {
|
||||
headerStyle.color = theme.palette.textContrast;
|
||||
cellStyle.fontWeight = theme.typography.fontWeightBold;
|
||||
|
||||
if (typeof cellStyle === 'object') {
|
||||
(cellStyle as React.CSSProperties).fontWeight =
|
||||
theme.typography.fontWeightBold;
|
||||
} else {
|
||||
const cellStyleFn = cellStyle as (
|
||||
data: any,
|
||||
rowData: T,
|
||||
column?: Column<T>,
|
||||
) => React.CSSProperties;
|
||||
cellStyle = (data, rowData, rowColumn) => {
|
||||
const style = cellStyleFn(data, rowData, rowColumn);
|
||||
return { ...style, fontWeight: theme.typography.fontWeightBold };
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -23,10 +23,6 @@
|
||||
"create-plugin": "backstage-cli create-plugin --scope internal",
|
||||
"remove-plugin": "backstage-cli remove-plugin"
|
||||
},
|
||||
"resolutions": {
|
||||
"graphql-language-service-interface": "2.8.2",
|
||||
"graphql-language-service-parser": "1.9.0"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*",
|
||||
|
||||
@@ -57,6 +57,26 @@ const streamToBuffer = (stream: Readable): Promise<Buffer> => {
|
||||
};
|
||||
|
||||
export class AwsS3Publish implements PublisherBase {
|
||||
private readonly storageClient: aws.S3;
|
||||
private readonly bucketName: string;
|
||||
private readonly legacyPathCasing: boolean;
|
||||
private readonly logger: Logger;
|
||||
private readonly bucketRootPath: string;
|
||||
|
||||
constructor(options: {
|
||||
storageClient: aws.S3;
|
||||
bucketName: string;
|
||||
legacyPathCasing: boolean;
|
||||
logger: Logger;
|
||||
bucketRootPath: string;
|
||||
}) {
|
||||
this.storageClient = options.storageClient;
|
||||
this.bucketName = options.bucketName;
|
||||
this.legacyPathCasing = options.legacyPathCasing;
|
||||
this.logger = options.logger;
|
||||
this.bucketRootPath = options.bucketRootPath;
|
||||
}
|
||||
|
||||
static fromConfig(config: Config, logger: Logger): PublisherBase {
|
||||
let bucketName = '';
|
||||
try {
|
||||
@@ -112,13 +132,13 @@ export class AwsS3Publish implements PublisherBase {
|
||||
'techdocs.legacyUseCaseSensitiveTripletPaths',
|
||||
) || false;
|
||||
|
||||
return new AwsS3Publish(
|
||||
return new AwsS3Publish({
|
||||
storageClient,
|
||||
bucketName,
|
||||
bucketRootPath,
|
||||
legacyPathCasing,
|
||||
logger,
|
||||
bucketRootPath,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private static buildCredentials(
|
||||
@@ -152,20 +172,6 @@ export class AwsS3Publish implements PublisherBase {
|
||||
return explicitCredentials;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly storageClient: aws.S3,
|
||||
private readonly bucketName: string,
|
||||
private readonly legacyPathCasing: boolean,
|
||||
private readonly logger: Logger,
|
||||
private readonly bucketRootPath: string,
|
||||
) {
|
||||
this.storageClient = storageClient;
|
||||
this.bucketName = bucketName;
|
||||
this.legacyPathCasing = legacyPathCasing;
|
||||
this.logger = logger;
|
||||
this.bucketRootPath = bucketRootPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the defined bucket exists. Being able to connect means the configuration is good
|
||||
* and the storage client will work.
|
||||
|
||||
@@ -47,6 +47,23 @@ import {
|
||||
const BATCH_CONCURRENCY = 3;
|
||||
|
||||
export class AzureBlobStoragePublish implements PublisherBase {
|
||||
private readonly storageClient: BlobServiceClient;
|
||||
private readonly containerName: string;
|
||||
private readonly legacyPathCasing: boolean;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(options: {
|
||||
storageClient: BlobServiceClient;
|
||||
containerName: string;
|
||||
legacyPathCasing: boolean;
|
||||
logger: Logger;
|
||||
}) {
|
||||
this.storageClient = options.storageClient;
|
||||
this.containerName = options.containerName;
|
||||
this.legacyPathCasing = options.legacyPathCasing;
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
||||
static fromConfig(config: Config, logger: Logger): PublisherBase {
|
||||
let containerName = '';
|
||||
try {
|
||||
@@ -95,24 +112,12 @@ export class AzureBlobStoragePublish implements PublisherBase {
|
||||
'techdocs.legacyUseCaseSensitiveTripletPaths',
|
||||
) || false;
|
||||
|
||||
return new AzureBlobStoragePublish(
|
||||
storageClient,
|
||||
containerName,
|
||||
legacyPathCasing,
|
||||
logger,
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly storageClient: BlobServiceClient,
|
||||
private readonly containerName: string,
|
||||
private readonly legacyPathCasing: boolean,
|
||||
private readonly logger: Logger,
|
||||
) {
|
||||
this.storageClient = storageClient;
|
||||
this.containerName = containerName;
|
||||
this.legacyPathCasing = legacyPathCasing;
|
||||
this.logger = logger;
|
||||
return new AzureBlobStoragePublish({
|
||||
storageClient: storageClient,
|
||||
containerName: containerName,
|
||||
legacyPathCasing: legacyPathCasing,
|
||||
logger: logger,
|
||||
});
|
||||
}
|
||||
|
||||
async getReadiness(): Promise<ReadinessResponse> {
|
||||
|
||||
@@ -41,6 +41,26 @@ import {
|
||||
} from './types';
|
||||
|
||||
export class GoogleGCSPublish implements PublisherBase {
|
||||
private readonly storageClient: Storage;
|
||||
private readonly bucketName: string;
|
||||
private readonly legacyPathCasing: boolean;
|
||||
private readonly logger: Logger;
|
||||
private readonly bucketRootPath: string;
|
||||
|
||||
constructor(options: {
|
||||
storageClient: Storage;
|
||||
bucketName: string;
|
||||
legacyPathCasing: boolean;
|
||||
logger: Logger;
|
||||
bucketRootPath: string;
|
||||
}) {
|
||||
this.storageClient = options.storageClient;
|
||||
this.bucketName = options.bucketName;
|
||||
this.legacyPathCasing = options.legacyPathCasing;
|
||||
this.logger = options.logger;
|
||||
this.bucketRootPath = options.bucketRootPath;
|
||||
}
|
||||
|
||||
static fromConfig(config: Config, logger: Logger): PublisherBase {
|
||||
let bucketName = '';
|
||||
try {
|
||||
@@ -84,27 +104,13 @@ export class GoogleGCSPublish implements PublisherBase {
|
||||
'techdocs.legacyUseCaseSensitiveTripletPaths',
|
||||
) || false;
|
||||
|
||||
return new GoogleGCSPublish(
|
||||
return new GoogleGCSPublish({
|
||||
storageClient,
|
||||
bucketName,
|
||||
legacyPathCasing,
|
||||
logger,
|
||||
bucketRootPath,
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly storageClient: Storage,
|
||||
private readonly bucketName: string,
|
||||
private readonly legacyPathCasing: boolean,
|
||||
private readonly logger: Logger,
|
||||
private readonly bucketRootPath: string,
|
||||
) {
|
||||
this.storageClient = storageClient;
|
||||
this.bucketName = bucketName;
|
||||
this.legacyPathCasing = legacyPathCasing;
|
||||
this.logger = logger;
|
||||
this.bucketRootPath = bucketRootPath;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,7 +63,11 @@ describe('local publisher', () => {
|
||||
|
||||
const mockConfig = new ConfigReader({});
|
||||
|
||||
const publisher = new LocalPublish(mockConfig, logger, testDiscovery);
|
||||
const publisher = LocalPublish.fromConfig(
|
||||
mockConfig,
|
||||
logger,
|
||||
testDiscovery,
|
||||
);
|
||||
const mockEntity = createMockEntity();
|
||||
const lowerMockEntity = createMockEntity(undefined, true);
|
||||
|
||||
@@ -90,7 +94,11 @@ describe('local publisher', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const publisher = new LocalPublish(mockConfig, logger, testDiscovery);
|
||||
const publisher = LocalPublish.fromConfig(
|
||||
mockConfig,
|
||||
logger,
|
||||
testDiscovery,
|
||||
);
|
||||
const mockEntity = createMockEntity();
|
||||
const lowerMockEntity = createMockEntity(undefined, true);
|
||||
|
||||
@@ -106,7 +114,11 @@ describe('local publisher', () => {
|
||||
|
||||
describe('docsRouter', () => {
|
||||
const mockConfig = new ConfigReader({});
|
||||
const publisher = new LocalPublish(mockConfig, logger, testDiscovery);
|
||||
const publisher = LocalPublish.fromConfig(
|
||||
mockConfig,
|
||||
logger,
|
||||
testDiscovery,
|
||||
);
|
||||
let app: express.Express;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -166,7 +178,7 @@ describe('local publisher', () => {
|
||||
legacyUseCaseSensitiveTripletPaths: true,
|
||||
},
|
||||
});
|
||||
const legacyPublisher = new LocalPublish(
|
||||
const legacyPublisher = LocalPublish.fromConfig(
|
||||
legacyConfig,
|
||||
logger,
|
||||
testDiscovery,
|
||||
|
||||
@@ -59,24 +59,37 @@ try {
|
||||
* called "static" at the root of techdocs-backend plugin.
|
||||
*/
|
||||
export class LocalPublish implements PublisherBase {
|
||||
private legacyPathCasing: boolean;
|
||||
private readonly legacyPathCasing: boolean;
|
||||
private readonly logger: Logger;
|
||||
private readonly discovery: PluginEndpointDiscovery;
|
||||
|
||||
// TODO: Use a static fromConfig method to create a LocalPublish instance, similar to aws/gcs publishers.
|
||||
// Move the logic of setting staticDocsDir based on config over to fromConfig,
|
||||
// and set the value as a class parameter.
|
||||
constructor(
|
||||
// @ts-ignore
|
||||
private readonly config: Config,
|
||||
private readonly logger: Logger,
|
||||
private readonly discovery: PluginEndpointDiscovery,
|
||||
) {
|
||||
this.config = config;
|
||||
this.logger = logger;
|
||||
this.discovery = discovery;
|
||||
this.legacyPathCasing =
|
||||
// TODO: Move the logic of setting staticDocsDir based on config over to
|
||||
// fromConfig, and set the value as a class parameter.
|
||||
constructor(options: {
|
||||
logger: Logger;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
legacyPathCasing: boolean;
|
||||
}) {
|
||||
this.logger = options.logger;
|
||||
this.discovery = options.discovery;
|
||||
this.legacyPathCasing = options.legacyPathCasing;
|
||||
}
|
||||
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
discovery: PluginEndpointDiscovery,
|
||||
): PublisherBase {
|
||||
const legacyPathCasing =
|
||||
config.getOptionalBoolean(
|
||||
'techdocs.legacyUseCaseSensitiveTripletPaths',
|
||||
) || false;
|
||||
|
||||
return new LocalPublish({
|
||||
logger,
|
||||
discovery,
|
||||
legacyPathCasing,
|
||||
});
|
||||
}
|
||||
|
||||
async getReadiness(): Promise<ReadinessResponse> {
|
||||
|
||||
@@ -54,6 +54,20 @@ const bufferToStream = (buffer: Buffer): Readable => {
|
||||
};
|
||||
|
||||
export class OpenStackSwiftPublish implements PublisherBase {
|
||||
private readonly storageClient: SwiftClient;
|
||||
private readonly containerName: string;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(options: {
|
||||
storageClient: SwiftClient;
|
||||
containerName: string;
|
||||
logger: Logger;
|
||||
}) {
|
||||
this.storageClient = options.storageClient;
|
||||
this.containerName = options.containerName;
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
||||
static fromConfig(config: Config, logger: Logger): PublisherBase {
|
||||
let containerName = '';
|
||||
try {
|
||||
@@ -78,17 +92,7 @@ export class OpenStackSwiftPublish implements PublisherBase {
|
||||
secret: openStackSwiftConfig.getString('credentials.secret'),
|
||||
});
|
||||
|
||||
return new OpenStackSwiftPublish(storageClient, containerName, logger);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly storageClient: SwiftClient,
|
||||
private readonly containerName: string,
|
||||
private readonly logger: Logger,
|
||||
) {
|
||||
this.storageClient = storageClient;
|
||||
this.containerName = containerName;
|
||||
this.logger = logger;
|
||||
return new OpenStackSwiftPublish({ storageClient, containerName, logger });
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -61,10 +61,10 @@ export class Publisher {
|
||||
return OpenStackSwiftPublish.fromConfig(config, logger);
|
||||
case 'local':
|
||||
logger.info('Creating Local publisher for TechDocs');
|
||||
return new LocalPublish(config, logger, discovery);
|
||||
return LocalPublish.fromConfig(config, logger, discovery);
|
||||
default:
|
||||
logger.info('Creating Local publisher for TechDocs');
|
||||
return new LocalPublish(config, logger, discovery);
|
||||
return LocalPublish.fromConfig(config, logger, discovery);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -75,6 +75,7 @@ export interface Config {
|
||||
logoutUrl?: string;
|
||||
issuer: string;
|
||||
cert: string;
|
||||
audience?: string;
|
||||
privateKey?: string;
|
||||
authnContext?: string[];
|
||||
identifierFormat?: string;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Logger } from 'winston';
|
||||
import { pickBy } from 'lodash';
|
||||
|
||||
import { PluginDatabaseManager } from '@backstage/backend-common';
|
||||
import { Config } from '@backstage/config';
|
||||
@@ -64,16 +65,20 @@ export class KeyStores {
|
||||
if (provider === 'firestore') {
|
||||
const settings = ks?.getConfig(provider);
|
||||
|
||||
const keyStore = await FirestoreKeyStore.create({
|
||||
projectId: settings?.getOptionalString('projectId'),
|
||||
keyFilename: settings?.getOptionalString('keyFilename'),
|
||||
host: settings?.getOptionalString('host'),
|
||||
port: settings?.getOptionalNumber('port'),
|
||||
ssl: settings?.getOptionalBoolean('ssl'),
|
||||
path: settings?.getOptionalString('path'),
|
||||
timeout: settings?.getOptionalNumber('timeout'),
|
||||
});
|
||||
|
||||
const keyStore = await FirestoreKeyStore.create(
|
||||
pickBy(
|
||||
{
|
||||
projectId: settings?.getOptionalString('projectId'),
|
||||
keyFilename: settings?.getOptionalString('keyFilename'),
|
||||
host: settings?.getOptionalString('host'),
|
||||
port: settings?.getOptionalNumber('port'),
|
||||
ssl: settings?.getOptionalBoolean('ssl'),
|
||||
path: settings?.getOptionalString('path'),
|
||||
timeout: settings?.getOptionalNumber('timeout'),
|
||||
},
|
||||
value => value !== undefined,
|
||||
),
|
||||
);
|
||||
await FirestoreKeyStore.verifyConnection(keyStore, logger);
|
||||
|
||||
return keyStore;
|
||||
|
||||
@@ -125,6 +125,7 @@ export const createSamlProvider = (
|
||||
callbackUrl: `${globalConfig.baseUrl}/${providerId}/handler/frame`,
|
||||
entryPoint: config.getString('entryPoint'),
|
||||
logoutUrl: config.getOptionalString('logoutUrl'),
|
||||
audience: config.getOptionalString('audience'),
|
||||
issuer: config.getString('issuer'),
|
||||
cert: config.getString('cert'),
|
||||
privateCert: config.getOptionalString('privateKey'),
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
```ts
|
||||
import { Build } from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import { BuildResult } from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import { BuildStatus } from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import { GitRepository } from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import { Logger as Logger_2 } from 'winston';
|
||||
import { PullRequestStatus } from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import { PullRequest } from '@backstage/plugin-azure-devops-common';
|
||||
import { PullRequestOptions } from '@backstage/plugin-azure-devops-common';
|
||||
import { RepoBuild } from '@backstage/plugin-azure-devops-common';
|
||||
import { WebApi } from 'azure-devops-node-api';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "AzureDevOpsApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -29,8 +29,6 @@ export class AzureDevOpsApi {
|
||||
projectName: string,
|
||||
repoName: string,
|
||||
): Promise<GitRepository>;
|
||||
// Warning: (ae-forgotten-export) The symbol "PullRequestOptions" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
getPullRequests(
|
||||
projectName: string,
|
||||
@@ -45,48 +43,11 @@ export class AzureDevOpsApi {
|
||||
): Promise<RepoBuild[]>;
|
||||
}
|
||||
|
||||
export { BuildResult };
|
||||
|
||||
export { BuildStatus };
|
||||
|
||||
// Warning: (ae-missing-release-tag) "createRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "PullRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type PullRequest = {
|
||||
pullRequestId?: number;
|
||||
repoName?: string;
|
||||
title?: string;
|
||||
uniqueName?: string;
|
||||
createdBy?: string;
|
||||
creationDate?: Date;
|
||||
sourceRefName?: string;
|
||||
targetRefName?: string;
|
||||
status?: PullRequestStatus;
|
||||
isDraft?: boolean;
|
||||
link: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RepoBuild" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type RepoBuild = {
|
||||
id?: number;
|
||||
title: string;
|
||||
link?: string;
|
||||
status?: BuildStatus;
|
||||
result?: BuildResult;
|
||||
queueTime?: Date;
|
||||
startTime?: Date;
|
||||
finishTime?: Date;
|
||||
source: string;
|
||||
uniqueName?: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RouterOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.9.8",
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/plugin-azure-devops-common": "^0.0.1",
|
||||
"@types/express": "^4.17.6",
|
||||
"azure-devops-node-api": "^11.0.1",
|
||||
"express": "^4.17.1",
|
||||
|
||||
@@ -16,17 +16,21 @@
|
||||
|
||||
import {
|
||||
Build,
|
||||
DefinitionReference,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import {
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
GitPullRequest,
|
||||
GitRepository,
|
||||
PullRequest,
|
||||
PullRequestStatus,
|
||||
RepoBuild,
|
||||
} from './types';
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
import {
|
||||
GitPullRequest,
|
||||
GitRepository,
|
||||
} from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import { mappedPullRequest, mappedRepoBuild } from './AzureDevOpsApi';
|
||||
|
||||
import { DefinitionReference } from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import { IdentityRef } from 'azure-devops-node-api/interfaces/common/VSSInterfaces';
|
||||
|
||||
describe('AzureDevOpsApi', () => {
|
||||
|
||||
@@ -15,17 +15,19 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
Build,
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
GitPullRequest,
|
||||
GitPullRequestSearchCriteria,
|
||||
GitRepository,
|
||||
PullRequest,
|
||||
PullRequestOptions,
|
||||
RepoBuild,
|
||||
} from './types';
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
import {
|
||||
GitPullRequest,
|
||||
GitPullRequestSearchCriteria,
|
||||
GitRepository,
|
||||
} from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
|
||||
import { Build } from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import { Logger } from 'winston';
|
||||
import { WebApi } from 'azure-devops-node-api';
|
||||
|
||||
|
||||
@@ -15,5 +15,3 @@
|
||||
*/
|
||||
|
||||
export { AzureDevOpsApi } from './AzureDevOpsApi';
|
||||
export { BuildResult, BuildStatus } from './types';
|
||||
export type { RepoBuild, PullRequest } from './types';
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
Build,
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import {
|
||||
GitPullRequest,
|
||||
GitPullRequestSearchCriteria,
|
||||
GitRepository,
|
||||
PullRequestStatus,
|
||||
} from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
|
||||
export { BuildResult, BuildStatus, PullRequestStatus };
|
||||
export type {
|
||||
Build,
|
||||
GitPullRequest,
|
||||
GitPullRequestSearchCriteria,
|
||||
GitRepository,
|
||||
};
|
||||
|
||||
export type RepoBuild = {
|
||||
id?: number;
|
||||
title: string;
|
||||
link?: string;
|
||||
status?: BuildStatus;
|
||||
result?: BuildResult;
|
||||
queueTime?: Date;
|
||||
startTime?: Date;
|
||||
finishTime?: Date;
|
||||
source: string;
|
||||
uniqueName?: string;
|
||||
};
|
||||
|
||||
export type PullRequest = {
|
||||
pullRequestId?: number;
|
||||
repoName?: string;
|
||||
title?: string;
|
||||
uniqueName?: string;
|
||||
createdBy?: string;
|
||||
creationDate?: Date;
|
||||
sourceRefName?: string;
|
||||
targetRefName?: string;
|
||||
status?: PullRequestStatus;
|
||||
isDraft?: boolean;
|
||||
link: string;
|
||||
};
|
||||
|
||||
export type PullRequestOptions = {
|
||||
top: number;
|
||||
status: PullRequestStatus;
|
||||
};
|
||||
@@ -13,6 +13,5 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { AzureDevOpsApi, BuildResult, BuildStatus } from './api';
|
||||
export type { RepoBuild, PullRequest } from './api';
|
||||
export { AzureDevOpsApi } from './api';
|
||||
export * from './service/router';
|
||||
|
||||
@@ -14,22 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import { AzureDevOpsApi } from '../api';
|
||||
import { createRouter } from './router';
|
||||
import { PullRequest, RepoBuild } from '../api/types';
|
||||
import {
|
||||
GitRepository,
|
||||
PullRequestStatus,
|
||||
} from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import {
|
||||
Build,
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
PullRequest,
|
||||
PullRequestStatus,
|
||||
RepoBuild,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
|
||||
import { AzureDevOpsApi } from '../api';
|
||||
import { Build } from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { GitRepository } from 'azure-devops-node-api/interfaces/GitInterfaces';
|
||||
import { createRouter } from './router';
|
||||
import express from 'express';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import request from 'supertest';
|
||||
|
||||
describe('createRouter', () => {
|
||||
let azureDevOpsApi: jest.Mocked<AzureDevOpsApi>;
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PullRequestOptions, PullRequestStatus } from '../api/types';
|
||||
import {
|
||||
PullRequestOptions,
|
||||
PullRequestStatus,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
import { WebApi, getPersonalAccessTokenHandler } from 'azure-devops-node-api';
|
||||
|
||||
import { AzureDevOpsApi } from '../api';
|
||||
|
||||
@@ -27,6 +27,42 @@ export enum BuildStatus {
|
||||
Postponed = 8,
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "PullRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type PullRequest = {
|
||||
pullRequestId?: number;
|
||||
repoName?: string;
|
||||
title?: string;
|
||||
uniqueName?: string;
|
||||
createdBy?: string;
|
||||
creationDate?: Date;
|
||||
sourceRefName?: string;
|
||||
targetRefName?: string;
|
||||
status?: PullRequestStatus;
|
||||
isDraft?: boolean;
|
||||
link: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "PullRequestOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type PullRequestOptions = {
|
||||
top: number;
|
||||
status: PullRequestStatus;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "PullRequestStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export enum PullRequestStatus {
|
||||
Abandoned = 2,
|
||||
Active = 1,
|
||||
All = 4,
|
||||
Completed = 3,
|
||||
NotSet = 0,
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RepoBuild" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -43,5 +79,12 @@ export type RepoBuild = {
|
||||
uniqueName?: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RepoBuildOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type RepoBuildOptions = {
|
||||
top?: number;
|
||||
};
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -80,3 +80,49 @@ export type RepoBuild = {
|
||||
source: string;
|
||||
uniqueName?: string;
|
||||
};
|
||||
|
||||
export type RepoBuildOptions = {
|
||||
top?: number;
|
||||
};
|
||||
|
||||
export enum PullRequestStatus {
|
||||
/**
|
||||
* Status not set. Default state.
|
||||
*/
|
||||
NotSet = 0,
|
||||
/**
|
||||
* Pull request is active.
|
||||
*/
|
||||
Active = 1,
|
||||
/**
|
||||
* Pull request is abandoned.
|
||||
*/
|
||||
Abandoned = 2,
|
||||
/**
|
||||
* Pull request is completed.
|
||||
*/
|
||||
Completed = 3,
|
||||
/**
|
||||
* Used in pull request search criteria to include all statuses.
|
||||
*/
|
||||
All = 4,
|
||||
}
|
||||
|
||||
export type PullRequest = {
|
||||
pullRequestId?: number;
|
||||
repoName?: string;
|
||||
title?: string;
|
||||
uniqueName?: string;
|
||||
createdBy?: string;
|
||||
creationDate?: Date;
|
||||
sourceRefName?: string;
|
||||
targetRefName?: string;
|
||||
status?: PullRequestStatus;
|
||||
isDraft?: boolean;
|
||||
link: string;
|
||||
};
|
||||
|
||||
export type PullRequestOptions = {
|
||||
top: number;
|
||||
status: PullRequestStatus;
|
||||
};
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.57",
|
||||
"azure-devops-node-api": "^11.0.1",
|
||||
"luxon": "^2.0.2",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { RepoBuild, RepoBuildOptions } from './types';
|
||||
import {
|
||||
RepoBuild,
|
||||
RepoBuildOptions,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const azureDevOpsApiRef = createApiRef<AzureDevOpsApi>({
|
||||
|
||||
@@ -14,9 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AzureDevOpsApi } from './AzureDevOpsApi';
|
||||
import { RepoBuild, RepoBuildOptions } from './types';
|
||||
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
RepoBuild,
|
||||
RepoBuildOptions,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
|
||||
import { AzureDevOpsApi } from './AzureDevOpsApi';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
|
||||
export class AzureDevOpsClient implements AzureDevOpsApi {
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
|
||||
export type RepoBuild = {
|
||||
id?: number;
|
||||
title: string;
|
||||
link?: string;
|
||||
status?: BuildStatus;
|
||||
result?: BuildResult;
|
||||
queueTime?: Date;
|
||||
source: string;
|
||||
};
|
||||
|
||||
export type RepoBuildOptions = {
|
||||
top?: number;
|
||||
};
|
||||
@@ -17,7 +17,7 @@
|
||||
import {
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
import { getBuildResultComponent, getBuildStateComponent } from './BuildTable';
|
||||
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
@@ -18,7 +18,8 @@ import { Box, Typography } from '@material-ui/core';
|
||||
import {
|
||||
BuildResult,
|
||||
BuildStatus,
|
||||
} from 'azure-devops-node-api/interfaces/BuildInterfaces';
|
||||
RepoBuild,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
import {
|
||||
Link,
|
||||
ResponseErrorPanel,
|
||||
@@ -34,7 +35,6 @@ import {
|
||||
|
||||
import { DateTime } from 'luxon';
|
||||
import React from 'react';
|
||||
import { RepoBuild } from '../../api/types';
|
||||
|
||||
export const getBuildResultComponent = (result: number | undefined) => {
|
||||
switch (result) {
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { RepoBuild, RepoBuildOptions } from '../api/types';
|
||||
import {
|
||||
RepoBuild,
|
||||
RepoBuildOptions,
|
||||
} from '@backstage/plugin-azure-devops-common';
|
||||
|
||||
import { AZURE_DEVOPS_DEFAULT_TOP } from '../constants';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
@@ -21,6 +21,14 @@ describe('normalizeEntityName', () => {
|
||||
expect(normalizeEntityName('User Name')).toBe('user_name');
|
||||
});
|
||||
|
||||
it('should normalize complex name to valid entity name', () => {
|
||||
expect(normalizeEntityName('User (Name)')).toBe('user_name');
|
||||
});
|
||||
|
||||
it('should normalize complex name to valid entity name without extra underscore', () => {
|
||||
expect(normalizeEntityName('User :(Name:)')).toBe('user_name');
|
||||
});
|
||||
|
||||
it('should normalize e-mail to valid entity name', () => {
|
||||
expect(normalizeEntityName('user.name@example.com')).toBe(
|
||||
'user.name_example.com',
|
||||
|
||||
@@ -15,8 +15,21 @@
|
||||
*/
|
||||
|
||||
export function normalizeEntityName(name: string): string {
|
||||
return name
|
||||
let cleaned = name
|
||||
.trim()
|
||||
.toLocaleLowerCase()
|
||||
.replace(/[^a-zA-Z0-9_\-\.]/g, '_');
|
||||
|
||||
// invalid to end with _
|
||||
while (cleaned.endsWith('_')) {
|
||||
cleaned = cleaned.substring(0, cleaned.length - 1);
|
||||
}
|
||||
|
||||
// cleans up format for groups like 'my group (Reader)'
|
||||
while (cleaned.includes('__')) {
|
||||
// replaceAll from node.js >= 15
|
||||
cleaned = cleaned.replace('__', '_');
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
@@ -445,6 +445,92 @@ describe('read microsoft graph', () => {
|
||||
// expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1);
|
||||
// expect(client.getGroupPhotoWithSizeLimit).toBeCalledWith('groupid', 120);
|
||||
});
|
||||
|
||||
it('should read security groups', async () => {
|
||||
async function* getExampleGroups() {
|
||||
yield {
|
||||
id: 'groupid',
|
||||
displayName: 'Group Name',
|
||||
description: 'Group Description',
|
||||
mail: 'group@example.com',
|
||||
mailNickname: 'df546d53-4f5f-4462-b371-d4a855787047',
|
||||
mailEnabled: false,
|
||||
securityEnabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
async function* getExampleGroupMembers(): AsyncIterable<GroupMember> {
|
||||
yield {
|
||||
'@odata.type': '#microsoft.graph.group',
|
||||
id: 'childgroupid',
|
||||
};
|
||||
yield {
|
||||
'@odata.type': '#microsoft.graph.user',
|
||||
id: 'userid',
|
||||
};
|
||||
}
|
||||
|
||||
client.getGroups.mockImplementation(getExampleGroups);
|
||||
client.getGroupMembers.mockImplementation(getExampleGroupMembers);
|
||||
client.getOrganization.mockResolvedValue({
|
||||
id: 'tenantid',
|
||||
displayName: 'Organization Name',
|
||||
});
|
||||
client.getGroupPhotoWithSizeLimit.mockResolvedValue(
|
||||
'data:image/jpeg;base64,...',
|
||||
);
|
||||
|
||||
const { groups, rootGroup } = await readMicrosoftGraphGroups(
|
||||
client,
|
||||
'tenantid',
|
||||
{
|
||||
groupFilter: 'securityEnabled eq true',
|
||||
},
|
||||
);
|
||||
|
||||
const expectedRootGroup = group({
|
||||
metadata: {
|
||||
annotations: {
|
||||
'graph.microsoft.com/tenant-id': 'tenantid',
|
||||
},
|
||||
name: 'organization_name',
|
||||
description: 'Organization Name',
|
||||
},
|
||||
spec: {
|
||||
type: 'root',
|
||||
profile: {
|
||||
displayName: 'Organization Name',
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
});
|
||||
expect(groups).toEqual([
|
||||
expectedRootGroup,
|
||||
group({
|
||||
metadata: {
|
||||
annotations: {
|
||||
'graph.microsoft.com/group-id': 'groupid',
|
||||
},
|
||||
name: 'group_name',
|
||||
description: 'Group Description',
|
||||
},
|
||||
spec: {
|
||||
type: 'team',
|
||||
profile: {
|
||||
displayName: 'Group Name',
|
||||
email: 'group@example.com',
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
expect(rootGroup).toEqual(expectedRootGroup);
|
||||
expect(client.getGroups).toBeCalledWith({
|
||||
filter: 'securityEnabled eq true',
|
||||
});
|
||||
expect(client.getGroupMembers).toBeCalledTimes(1);
|
||||
expect(client.getGroupMembers).toBeCalledWith('groupid');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveRelations', () => {
|
||||
|
||||
@@ -251,6 +251,13 @@ export async function readMicrosoftGraphOrganization(
|
||||
return { rootGroup };
|
||||
}
|
||||
|
||||
function extractGroupName(group: MicrosoftGraph.Group): string {
|
||||
if (group.securityEnabled) {
|
||||
return group.displayName as string;
|
||||
}
|
||||
return (group.mailNickname || group.displayName) as string;
|
||||
}
|
||||
|
||||
export async function defaultGroupTransformer(
|
||||
group: MicrosoftGraph.Group,
|
||||
groupPhoto?: string,
|
||||
@@ -259,7 +266,7 @@ export async function defaultGroupTransformer(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const name = normalizeEntityName(group.mailNickname || group.displayName);
|
||||
const name = normalizeEntityName(extractGroupName(group));
|
||||
const entity: GroupEntity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
|
||||
@@ -79,10 +79,14 @@ export class KubernetesBackendClient implements KubernetesApi {
|
||||
}
|
||||
|
||||
async getClusters(): Promise<{ name: string; authProvider: string }[]> {
|
||||
const idToken = await this.identityApi.getIdToken();
|
||||
const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}/clusters`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
...(idToken && { Authorization: `Bearer ${idToken}` }),
|
||||
},
|
||||
});
|
||||
|
||||
return (await this.handleResponse(response)).items;
|
||||
|
||||
@@ -5486,10 +5486,10 @@
|
||||
resolved "https://registry.npmjs.org/@spotify/eslint-config-react/-/eslint-config-react-10.0.0.tgz#6f83ada05f79b49c1f9def5b8815e3231ed24969"
|
||||
integrity sha512-MozX6W3aMp7EQPliuUQYI58Ni5vh65mItXMG0CgZBj0v1ZEeZVM5XS/nqhsCaIHYXskmZM2O1qqLFaEg5PqGdg==
|
||||
|
||||
"@spotify/eslint-config-typescript@^10.0.0":
|
||||
version "10.0.0"
|
||||
resolved "https://registry.npmjs.org/@spotify/eslint-config-typescript/-/eslint-config-typescript-10.0.0.tgz#4df7074f3f4ef31d76c617e55d335f9a36cfed5b"
|
||||
integrity sha512-qR4WOU3gJrpz26O8BlNbXas4Yj93NeVH7yvULVYO2j9bCAEZJu2sfl1BGfOy4qAsYGutZhJtNwMqK0Rl4DcFHQ==
|
||||
"@spotify/eslint-config-typescript@^12.0.0":
|
||||
version "12.0.0"
|
||||
resolved "https://registry.npmjs.org/@spotify/eslint-config-typescript/-/eslint-config-typescript-12.0.0.tgz#4c7af3f74a47668bec0c860b72e2a0103e78a138"
|
||||
integrity sha512-nMVll8ZkN/W8+IHn6Iz3YzCKW0qhrn3TVfyxkAr3qmXm5cex+GzyUdZEuxb8rdN2inZL6A1Il2NFfO5p/UKxog==
|
||||
|
||||
"@spotify/prettier-config@^11.0.0":
|
||||
version "11.0.0"
|
||||
@@ -15591,20 +15591,20 @@ graphql-extensions@^0.15.0:
|
||||
apollo-server-env "^3.1.0"
|
||||
apollo-server-types "^0.9.0"
|
||||
|
||||
graphql-language-service-interface@2.8.2, graphql-language-service-interface@^2.9.0:
|
||||
version "2.8.2"
|
||||
resolved "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.8.2.tgz#b3bb2aef7eaf0dff0b4ea419fa412c5f66fa268b"
|
||||
integrity sha512-otbOQmhgkAJU1QJgQkMztNku6SbJLu/uodoFOYOOtJsizTjrMs93vkYaHCcYnLA3oi1Goj27XcHjMnRCYQOZXQ==
|
||||
graphql-language-service-interface@^2.9.0:
|
||||
version "2.9.1"
|
||||
resolved "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.9.1.tgz#be0b11b06b78730ea9d250e0e2290e7ed9c8d283"
|
||||
integrity sha512-yGsE67fxJBXxY82+rLDMvUpmzpOUM8XFB+k+xOTUyABWs27osKaoGiuDDXAVGg1adhm+cpunWbipe763ZJkAVA==
|
||||
dependencies:
|
||||
graphql-language-service-parser "^1.9.0"
|
||||
graphql-language-service-types "^1.8.0"
|
||||
graphql-language-service-utils "^2.5.1"
|
||||
graphql-language-service-parser "^1.10.0"
|
||||
graphql-language-service-types "^1.8.3"
|
||||
graphql-language-service-utils "^2.6.0"
|
||||
vscode-languageserver-types "^3.15.1"
|
||||
|
||||
graphql-language-service-parser@1.9.0, graphql-language-service-parser@^1.10.0, graphql-language-service-parser@^1.9.0:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.9.0.tgz#79af21294119a0a7e81b6b994a1af36833bab724"
|
||||
integrity sha512-B5xPZLbBmIp0kHvpY1Z35I5DtPoDK9wGxQVRDIzcBaiIvAmlTrDvjo3bu7vKREdjFbYKvWNgrEWENuprMbF17Q==
|
||||
graphql-language-service-parser@^1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.10.0.tgz#116f4be849754f6afb4c196421a43fe96d87b278"
|
||||
integrity sha512-cLExv0EjqT2hsKdwVTPmKU6eMfjZAjxqywgCPnWD48eJn6tyuePMyG7ye+jpX1PRPPx/cDHfFJGf8sUclchvng==
|
||||
dependencies:
|
||||
graphql-language-service-types "^1.8.0"
|
||||
|
||||
@@ -15618,13 +15618,10 @@ graphql-language-service-types@^1.8.2:
|
||||
resolved "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.8.2.tgz#50ae56f69cc24fcfc3daa129b68b0eb9421e8578"
|
||||
integrity sha512-Sj07RHnMwAhEvAt7Jdt1l/x56ZpoNh+V6g+T58CF6GiYqI5l4vXqqRB4d4xHDcNQX98GpJfnf3o8BqPgP3C5Sw==
|
||||
|
||||
graphql-language-service-utils@^2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.5.1.tgz#832ad4b0a9da03fdded756932c27e057ccf71302"
|
||||
integrity sha512-Lzz723cYrYlVN4WVzIyFGg3ogoe+QYAIBfdtDboiIILoy0FTmqbyC2TOErqbmWKqO4NK9xDA95cSRFbWiHYj0g==
|
||||
dependencies:
|
||||
graphql-language-service-types "^1.8.0"
|
||||
nullthrows "^1.0.0"
|
||||
graphql-language-service-types@^1.8.3:
|
||||
version "1.8.3"
|
||||
resolved "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.8.3.tgz#d7d688d74c122c4d9cc4cceae761a1f2a3c396a1"
|
||||
integrity sha512-m+RHnlGkKDcesW/gC4M7I2pSmWJB84uWS6LtnjplO/07JN312nJCJYCwV/DBny2m1fmSOxN7H/o+JW0l56KwBA==
|
||||
|
||||
graphql-language-service-utils@^2.5.3:
|
||||
version "2.5.3"
|
||||
@@ -15634,6 +15631,14 @@ graphql-language-service-utils@^2.5.3:
|
||||
graphql-language-service-types "^1.8.0"
|
||||
nullthrows "^1.0.0"
|
||||
|
||||
graphql-language-service-utils@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.6.0.tgz#d04904641248167ccbb381d8705ba97daa784954"
|
||||
integrity sha512-idqwmbREixhDuQMcYp8WH0btQT02xZny8MO/HduNTVjnPrmTYnZUbpZ9AejdflmaKoS0o8nNvgXQ0GpIOzbG5g==
|
||||
dependencies:
|
||||
graphql-language-service-types "^1.8.3"
|
||||
nullthrows "^1.0.0"
|
||||
|
||||
graphql-language-service@^3.1.6:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-3.2.0.tgz#e0eb6d5dea2cab92549a253d7a6b4fa0cce178b7"
|
||||
|
||||
Reference in New Issue
Block a user