Merge branch 'master' of https://github.com/backstage/backstage into marley/7678-pull-request-dashboard

This commit is contained in:
Marley Powell
2021-11-09 15:42:45 +00:00
84 changed files with 1577 additions and 697 deletions
+15 -10
View File
@@ -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;
+3 -42
View File
@@ -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;
};
+1 -2
View File
@@ -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';
+43
View File
@@ -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)
```
+46
View File
@@ -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;
};
-1
View File
@@ -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 {
-34
View File
@@ -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';
@@ -158,7 +158,11 @@ export const CoverageHistoryChart = () => {
margin={{ right: 48, top: 32 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="timestamp" tickFormatter={formatDateToHuman} />
<XAxis
dataKey="timestamp"
tickFormatter={formatDateToHuman}
reversed
/>
<YAxis dataKey="line.percentage" />
<YAxis dataKey="branch.percentage" />
<Tooltip labelFormatter={formatDateToHuman} />
@@ -14,11 +14,11 @@
* limitations under the License.
*/
import { Config } from '@backstage/config';
import { getVoidLogger, loadBackendConfig } from '@backstage/backend-common';
import { getVoidLogger } from '@backstage/backend-common';
import { Config, ConfigReader } from '@backstage/config';
import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common';
import express from 'express';
import request from 'supertest';
import { KubernetesFanOutHandler } from './KubernetesFanOutHandler';
import {
ClusterDetails,
FetchResponseWrapper,
@@ -27,8 +27,8 @@ import {
KubernetesServiceLocator,
ObjectFetchParams,
} from '../types/types';
import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common';
import { KubernetesBuilder } from './KubernetesBuilder';
import { KubernetesFanOutHandler } from './KubernetesFanOutHandler';
describe('KubernetesBuilder', () => {
let app: express.Express;
@@ -37,7 +37,12 @@ describe('KubernetesBuilder', () => {
beforeAll(async () => {
const logger = getVoidLogger();
config = await loadBackendConfig({ logger, argv: [] });
config = new ConfigReader({
kubernetes: {
serviceLocatorMethod: { type: 'multiTenant' },
clusterLocatorMethods: [{ type: 'config', clusters: [] }],
},
});
const clusters: ClusterDetails[] = [
{
@@ -14,11 +14,8 @@
* limitations under the License.
*/
import {
getVoidLogger,
loadBackendConfig,
SingleHostDiscovery,
} from '@backstage/backend-common';
import { getVoidLogger, SingleHostDiscovery } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { Request, Response } from 'express';
import * as http from 'http';
import { createProxyMiddleware, Options } from 'http-proxy-middleware';
@@ -35,7 +32,14 @@ const mockCreateProxyMiddleware = createProxyMiddleware as jest.MockedFunction<
describe('createRouter', () => {
it('works', async () => {
const logger = getVoidLogger();
const config = await loadBackendConfig({ logger, argv: [] });
const config = new ConfigReader({
backend: {
baseUrl: 'https://example.com:7000',
listen: {
port: 7000,
},
},
});
const discovery = SingleHostDiscovery.fromConfig(config);
const router = await createRouter({
config,
@@ -254,7 +254,7 @@ describe('createPublishGithubPullRequestAction', () => {
});
});
describe('with executable file', () => {
describe('with executable file mode 755', () => {
let input: GithubPullRequestActionInput;
let ctx: ActionContext<GithubPullRequestActionInput>;
@@ -268,9 +268,9 @@ describe('createPublishGithubPullRequestAction', () => {
mockFs({
[workspacePath]: {
'file.txt': mockFs.file({
content: 'Hello there!',
mode: 33277, // File mode: 100755
'hello.sh': mockFs.file({
content: 'echo Hello there!',
mode: 0o100755,
}),
},
});
@@ -297,8 +297,76 @@ describe('createPublishGithubPullRequestAction', () => {
{
commit: 'Create my new app',
files: {
'file.txt': {
content: Buffer.from('Hello there!').toString('base64'),
'hello.sh': {
content: Buffer.from('echo Hello there!').toString('base64'),
encoding: 'base64',
mode: '100755',
},
},
},
],
});
});
it('creates outputs for the url', async () => {
await instance.handler(ctx);
expect(ctx.output).toHaveBeenCalledWith(
'remoteUrl',
'https://github.com/myorg/myrepo/pull/123',
);
});
afterEach(() => {
mockFs.restore();
jest.resetAllMocks();
});
});
describe('with executable file mode 775', () => {
let input: GithubPullRequestActionInput;
let ctx: ActionContext<GithubPullRequestActionInput>;
beforeEach(() => {
input = {
repoUrl: 'github.com?owner=myorg&repo=myrepo',
title: 'Create my new app',
branchName: 'new-app',
description: 'This PR is really good',
};
mockFs({
[workspacePath]: {
'hello.sh': mockFs.file({
content: 'echo Hello there!',
mode: 0o100775,
}),
},
});
ctx = {
createTemporaryDirectory: jest.fn(),
output: jest.fn(),
logger: getRootLogger(),
logStream: new Writable(),
input,
workspacePath,
};
});
it('creates a pull request', async () => {
await instance.handler(ctx);
expect(fakeClient.createPullRequest).toHaveBeenCalledWith({
owner: 'myorg',
repo: 'myrepo',
title: 'Create my new app',
head: 'new-app',
body: 'This PR is really good',
changes: [
{
commit: 'Create my new app',
files: {
'hello.sh': {
content: Buffer.from('echo Hello there!').toString('base64'),
encoding: 'base64',
mode: '100755',
},
@@ -16,7 +16,7 @@
import fs from 'fs-extra';
import path from 'path';
import { parseRepoUrl } from './util';
import { parseRepoUrl, isExecutable } from './util';
import {
GithubCredentialsProvider,
@@ -202,10 +202,11 @@ export const createPublishGithubPullRequestAction = ({
.readFileSync(absPath)
.toString('base64');
const fileStat = fs.statSync(absPath);
const isExecutable = fileStat.mode === 33277; // aka. 100755
// See the properties of tree items
// in https://docs.github.com/en/rest/reference/git#trees
const githubTreeItemMode = isExecutable ? '100755' : '100644';
const githubTreeItemMode = isExecutable(fileStat.mode)
? '100755'
: '100644';
// Always use base64 encoding to avoid doubling a binary file in size
// due to interpreting a binary file as utf-8 and sending github
// the utf-8 encoded content.
@@ -15,7 +15,7 @@
*/
import path from 'path';
import { getRepoSourceDirectory } from './util';
import { getRepoSourceDirectory, isExecutable } from './util';
describe('getRepoSourceDirectory', () => {
it('should return workspace root if no sub folder is given', () => {
@@ -55,3 +55,33 @@ describe('getRepoSourceDirectory', () => {
).toEqual(path.join('/', 'var', 'workspace', 'absolute', 'secret'));
});
});
describe('isExecutable', () => {
it('should return true for file mode 777', () => {
expect(isExecutable(0o100777)).toBe(true);
});
it('should return true for file mode 775', () => {
expect(isExecutable(0o100775)).toBe(true);
});
it('should return true for file mode 755', () => {
expect(isExecutable(0o100755)).toBe(true);
});
it('should return true for file mode 700', () => {
expect(isExecutable(0o100700)).toBe(true);
});
it('should return true for file mode 770', () => {
expect(isExecutable(0o100770)).toBe(true);
});
it('should return true for file mode 670', () => {
expect(isExecutable(0o100670)).toBe(true);
});
it('should return false for file mode 644', () => {
expect(isExecutable(0o100644)).toBe(false);
});
it('should return false for file mode 600', () => {
expect(isExecutable(0o100600)).toBe(false);
});
it('should return false for file mode 640', () => {
expect(isExecutable(0o100640)).toBe(false);
});
});
@@ -101,3 +101,8 @@ export const parseRepoUrl = (
return { host, owner, repo, organization, workspace, project };
};
export const isExecutable = (fileMode: number) => {
const executeBitMask = 0o000111;
const res = fileMode & executeBitMask;
return res > 0;
};