Added scaffolder support for publishing to Gerrit
This patch enables support for publishing the workspace content to new project in Gerrit. This can be broken down to three things: * "resolveUrl" for the Gerrit integration have been updated to handle absolute paths correctly. * "RepoUrlPicker" has been updated to handle gerrit hosts. * A new scaffolder action has been added that will publish the workspace content to a newly created Gerrit project. Signed-off-by: Niklas Aronsson <niklasar@axis.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
A new scaffolder action has been added: `gerrit:publish`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
Gerrit Integration: Handle absolute paths in `resolveUrl` properly.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': minor
|
||||
---
|
||||
|
||||
Gerrit Integration: Implemented a `RepoUrlPicker` for Gerrit.
|
||||
@@ -97,6 +97,24 @@ describe('GerritIntegration', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolves with an absolute url', () => {
|
||||
it('works for valid urls', () => {
|
||||
const integration = new GerritIntegration({
|
||||
host: 'gerrit-review.example.com',
|
||||
gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles',
|
||||
} as any);
|
||||
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: '/catalog-info.yaml',
|
||||
base: 'https://gerrit-review.example.com/gitiles/repo/+/refs/heads/master/',
|
||||
}),
|
||||
).toBe(
|
||||
'https://gerrit-review.example.com/gitiles/repo/+/refs/heads/master/catalog-info.yaml',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('resolve edit URL', () => {
|
||||
const integration = new GerritIntegration({
|
||||
host: 'gerrit-review.example.com',
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
GerritIntegrationConfig,
|
||||
readGerritIntegrationConfigs,
|
||||
} from './config';
|
||||
import { parseGerritGitilesUrl, builldGerritGitilesUrl } from './core';
|
||||
|
||||
/**
|
||||
* A Gerrit based integration.
|
||||
@@ -58,6 +59,10 @@ export class GerritIntegration implements ScmIntegration {
|
||||
}): string {
|
||||
const { url, base, lineNumber } = options;
|
||||
let updated;
|
||||
if (url.startsWith('/')) {
|
||||
const { branch, project } = parseGerritGitilesUrl(this.config, base);
|
||||
return builldGerritGitilesUrl(this.config, project, branch, url);
|
||||
}
|
||||
if (url) {
|
||||
updated = new URL(url, base);
|
||||
} else {
|
||||
|
||||
@@ -20,6 +20,7 @@ import fetch from 'cross-fetch';
|
||||
import { setupRequestMockHandlers } from '@backstage/test-utils';
|
||||
import { GerritIntegrationConfig } from './config';
|
||||
import {
|
||||
builldGerritGitilesUrl,
|
||||
getGerritBranchApiUrl,
|
||||
getGerritCloneRepoUrl,
|
||||
getGerritRequestOptions,
|
||||
@@ -32,6 +33,20 @@ describe('gerrit core', () => {
|
||||
const worker = setupServer();
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
describe('builldGerritGitilesUrl', () => {
|
||||
it('can create an url from arguments', () => {
|
||||
const config: GerritIntegrationConfig = {
|
||||
host: 'gerrit.com',
|
||||
gitilesBaseUrl: 'https://gerrit.com/gitiles',
|
||||
};
|
||||
expect(
|
||||
builldGerritGitilesUrl(config, 'repo', 'dev', 'catalog-info.yaml'),
|
||||
).toEqual(
|
||||
'https://gerrit.com/gitiles/repo/+/refs/heads/dev/catalog-info.yaml',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGerritRequestOptions', () => {
|
||||
it('adds headers when a password is specified', () => {
|
||||
const authRequest: GerritIntegrationConfig = {
|
||||
|
||||
@@ -70,6 +70,26 @@ export function parseGerritGitilesUrl(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Gerrit Gitiles url that targets a specific path.
|
||||
*
|
||||
* @param config - A Gerrit provider config.
|
||||
* @param project - The name of the git project
|
||||
* @param branch - The branch we will target.
|
||||
* @param filePath - The absolute file path.
|
||||
* @public
|
||||
*/
|
||||
export function builldGerritGitilesUrl(
|
||||
config: GerritIntegrationConfig,
|
||||
project: string,
|
||||
branch: string,
|
||||
filePath: string,
|
||||
): string {
|
||||
return `${
|
||||
config.gitilesBaseUrl
|
||||
}/${project}/+/refs/heads/${branch}/${trimStart(filePath, '/')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the authentication prefix.
|
||||
*
|
||||
|
||||
@@ -247,6 +247,19 @@ export function createPublishFileAction(): TemplateAction<{
|
||||
path: string;
|
||||
}>;
|
||||
|
||||
// @public
|
||||
export function createPublishGerritAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
config: Config;
|
||||
}): TemplateAction<{
|
||||
repoUrl: string;
|
||||
description: string;
|
||||
defaultBranch?: string | undefined;
|
||||
gitCommitMessage?: string | undefined;
|
||||
gitAuthorName?: string | undefined;
|
||||
gitAuthorEmail?: string | undefined;
|
||||
}>;
|
||||
|
||||
// @public
|
||||
export function createPublishGithubAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
createPublishBitbucketAction,
|
||||
createPublishBitbucketCloudAction,
|
||||
createPublishBitbucketServerAction,
|
||||
createPublishGerritAction,
|
||||
createPublishGithubAction,
|
||||
createPublishGithubPullRequestAction,
|
||||
createPublishGitlabAction,
|
||||
@@ -111,6 +112,10 @@ export const createBuiltinActions = (
|
||||
reader,
|
||||
additionalTemplateFilters,
|
||||
}),
|
||||
createPublishGerritAction({
|
||||
integrations,
|
||||
config,
|
||||
}),
|
||||
createPublishGithubAction({
|
||||
integrations,
|
||||
config,
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
jest.mock('../helpers');
|
||||
|
||||
import { createPublishGerritAction } from './gerrit';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { PassThrough } from 'stream';
|
||||
import { initRepoAndPush } from '../helpers';
|
||||
|
||||
describe('publish:gerrit', () => {
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
gerrit: [
|
||||
{
|
||||
host: 'gerrithost.org',
|
||||
username: 'gerrituser',
|
||||
password: 'usertoken',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const description = 'for the lols';
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const action = createPublishGerritAction({ integrations, config });
|
||||
const mockContext = {
|
||||
input: {
|
||||
repoUrl:
|
||||
'gerrithost.org?owner=owner&workspace=parent&project=project&repo=repo',
|
||||
description,
|
||||
},
|
||||
workspacePath: 'lol',
|
||||
logger: getVoidLogger(),
|
||||
logStream: new PassThrough(),
|
||||
output: jest.fn(),
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
};
|
||||
const server = setupServer();
|
||||
setupRequestMockHandlers(server);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should throw an error when the repoUrl is not well formed', async () => {
|
||||
await expect(
|
||||
action.handler({
|
||||
...mockContext,
|
||||
input: { repoUrl: 'gerrithost.org?workspace=w&repo=repo', description },
|
||||
}),
|
||||
).rejects.toThrow(/missing owner/);
|
||||
|
||||
await expect(
|
||||
action.handler({
|
||||
...mockContext,
|
||||
input: { repoUrl: 'gerrithost.org?workspace=w&owner=o', description },
|
||||
}),
|
||||
).rejects.toThrow(/missing repo/);
|
||||
});
|
||||
|
||||
it('should throw if there is no integration config provided', async () => {
|
||||
await expect(
|
||||
action.handler({
|
||||
...mockContext,
|
||||
input: {
|
||||
repoUrl: 'missing.com?workspace=w&owner=o&repo=repo',
|
||||
description,
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow(/No matching integration configuration/);
|
||||
});
|
||||
|
||||
it('can correctly create a new project', async () => {
|
||||
expect.assertions(5);
|
||||
server.use(
|
||||
rest.put('https://gerrithost.org/a/projects/repo', (req, res, ctx) => {
|
||||
expect(req.headers.get('Authorization')).toBe(
|
||||
'Basic Z2Vycml0dXNlcjp1c2VydG9rZW4=',
|
||||
);
|
||||
expect(req.body).toEqual({
|
||||
create_empty_commit: false,
|
||||
owners: ['owner'],
|
||||
description,
|
||||
parent: 'workspace',
|
||||
});
|
||||
return res(
|
||||
ctx.status(201),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.json({}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
repoUrl: 'gerrithost.org?workspace=workspace&owner=owner&repo=repo',
|
||||
},
|
||||
});
|
||||
|
||||
expect(initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: mockContext.workspacePath,
|
||||
remoteUrl: 'https://gerrithost.org/a/repo',
|
||||
defaultBranch: 'master',
|
||||
auth: { username: 'gerrituser', password: 'usertoken' },
|
||||
logger: mockContext.logger,
|
||||
commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'),
|
||||
gitAuthorInfo: {},
|
||||
});
|
||||
|
||||
expect(mockContext.output).toHaveBeenCalledWith(
|
||||
'remoteUrl',
|
||||
'https://gerrithost.org/a/repo',
|
||||
);
|
||||
expect(mockContext.output).toHaveBeenCalledWith(
|
||||
'repoContentsUrl',
|
||||
'https://gerrithost.org/repo/+/refs/heads/master',
|
||||
);
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright 2022 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 crypto from 'crypto';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
GerritIntegrationConfig,
|
||||
getGerritRequestOptions,
|
||||
ScmIntegrationRegistry,
|
||||
} from '@backstage/integration';
|
||||
import { createTemplateAction } from '../../createTemplateAction';
|
||||
import { getRepoSourceDirectory, parseRepoUrl } from './util';
|
||||
import fetch, { Response, RequestInit } from 'node-fetch';
|
||||
import { initRepoAndPush } from '../helpers';
|
||||
|
||||
const createGerritProject = async (
|
||||
config: GerritIntegrationConfig,
|
||||
options: {
|
||||
projectName: string;
|
||||
parent: string;
|
||||
owner: string;
|
||||
description: string;
|
||||
},
|
||||
): Promise<void> => {
|
||||
const { projectName, parent, owner, description } = options;
|
||||
|
||||
const fetchOptions: RequestInit = {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
parent,
|
||||
description,
|
||||
owners: [owner],
|
||||
create_empty_commit: false,
|
||||
}),
|
||||
headers: {
|
||||
...getGerritRequestOptions(config).headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
const response: Response = await fetch(
|
||||
`${config.baseUrl}/a/projects/${encodeURIComponent(projectName)}`,
|
||||
fetchOptions,
|
||||
);
|
||||
if (response.status !== 201) {
|
||||
throw new Error(
|
||||
`Unable to create repository, ${response.status} ${
|
||||
response.statusText
|
||||
}, ${await response.text()}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const generateCommitMessage = (
|
||||
config: Config,
|
||||
commitSubject?: string,
|
||||
): string => {
|
||||
const changeId = crypto.randomBytes(20).toString('hex');
|
||||
const msg = `${
|
||||
config.getOptionalString('scaffolder.defaultCommitMessage') || commitSubject
|
||||
}\n\nChange-Id: I${changeId}`;
|
||||
return msg;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new action that initializes a git repository of the content in the workspace
|
||||
* and publishes it to a Gerrit instance.
|
||||
* @public
|
||||
*/
|
||||
export function createPublishGerritAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
config: Config;
|
||||
}) {
|
||||
const { integrations, config } = options;
|
||||
|
||||
return createTemplateAction<{
|
||||
repoUrl: string;
|
||||
description: string;
|
||||
defaultBranch?: string;
|
||||
gitCommitMessage?: string;
|
||||
gitAuthorName?: string;
|
||||
gitAuthorEmail?: string;
|
||||
}>({
|
||||
id: 'publish:gerrit',
|
||||
description:
|
||||
'Initializes a git repository of the content in the workspace, and publishes it to Gerrit.',
|
||||
schema: {
|
||||
input: {
|
||||
type: 'object',
|
||||
required: ['repoUrl'],
|
||||
properties: {
|
||||
repoUrl: {
|
||||
title: 'Repository Location',
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
title: 'Repository Description',
|
||||
type: 'string',
|
||||
},
|
||||
defaultBranch: {
|
||||
title: 'Default Branch',
|
||||
type: 'string',
|
||||
description: `Sets the default branch on the repository. The default value is 'master'`,
|
||||
},
|
||||
gitCommitMessage: {
|
||||
title: 'Git Commit Message',
|
||||
type: 'string',
|
||||
description: `Sets the commit message on the repository. The default value is 'initial commit'`,
|
||||
},
|
||||
gitAuthorName: {
|
||||
title: 'Default Author Name',
|
||||
type: 'string',
|
||||
description: `Sets the default author name for the commit. The default value is 'Scaffolder'`,
|
||||
},
|
||||
gitAuthorEmail: {
|
||||
title: 'Default Author Email',
|
||||
type: 'string',
|
||||
description: `Sets the default author email for the commit.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
remoteUrl: {
|
||||
title: 'A URL to the repository with the provider',
|
||||
type: 'string',
|
||||
},
|
||||
repoContentsUrl: {
|
||||
title: 'A URL to the root of the repository',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
const {
|
||||
repoUrl,
|
||||
description,
|
||||
defaultBranch = 'master',
|
||||
gitAuthorName,
|
||||
gitAuthorEmail,
|
||||
gitCommitMessage = 'initial commit',
|
||||
} = ctx.input;
|
||||
const { repo, host, owner, workspace } = parseRepoUrl(
|
||||
repoUrl,
|
||||
integrations,
|
||||
);
|
||||
|
||||
const integrationConfig = integrations.gerrit.byHost(host);
|
||||
|
||||
if (!integrationConfig) {
|
||||
throw new InputError(
|
||||
`No matching integration configuration for host ${host}, please check your integrations config`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!owner) {
|
||||
throw new InputError(
|
||||
`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing owner`,
|
||||
);
|
||||
}
|
||||
if (!workspace) {
|
||||
throw new InputError(
|
||||
`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`,
|
||||
);
|
||||
}
|
||||
|
||||
await createGerritProject(integrationConfig.config, {
|
||||
description,
|
||||
owner: owner,
|
||||
projectName: repo,
|
||||
parent: workspace,
|
||||
});
|
||||
const auth = {
|
||||
username: integrationConfig.config.username!,
|
||||
password: integrationConfig.config.password!,
|
||||
};
|
||||
const gitAuthorInfo = {
|
||||
name: gitAuthorName
|
||||
? gitAuthorName
|
||||
: config.getOptionalString('scaffolder.defaultAuthor.name'),
|
||||
email: gitAuthorEmail
|
||||
? gitAuthorEmail
|
||||
: config.getOptionalString('scaffolder.defaultAuthor.email'),
|
||||
};
|
||||
|
||||
const remoteUrl = `${integrationConfig.config.cloneUrl}/a/${repo}`;
|
||||
await initRepoAndPush({
|
||||
dir: getRepoSourceDirectory(ctx.workspacePath, undefined),
|
||||
remoteUrl,
|
||||
auth,
|
||||
defaultBranch,
|
||||
logger: ctx.logger,
|
||||
commitMessage: generateCommitMessage(config, gitCommitMessage),
|
||||
gitAuthorInfo,
|
||||
});
|
||||
|
||||
const repoContentsUrl = `${integrationConfig.config.gitilesBaseUrl}/${repo}/+/refs/heads/${defaultBranch}`;
|
||||
ctx.output('remoteUrl', remoteUrl);
|
||||
ctx.output('repoContentsUrl', repoContentsUrl);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -19,6 +19,7 @@ export { createPublishBitbucketAction } from './bitbucket';
|
||||
export { createPublishBitbucketCloudAction } from './bitbucketCloud';
|
||||
export { createPublishBitbucketServerAction } from './bitbucketServer';
|
||||
export { createPublishFileAction } from './file';
|
||||
export { createPublishGerritAction } from './gerrit';
|
||||
export { createPublishGithubAction } from './github';
|
||||
export { createPublishGithubPullRequestAction } from './githubPullRequest';
|
||||
export type {
|
||||
|
||||
@@ -194,6 +194,7 @@ export interface RepoUrlPickerUiOptions {
|
||||
requestUserCredentials?: {
|
||||
secretsKey: string;
|
||||
additionalScopes?: {
|
||||
gerrit?: string[];
|
||||
github?: string[];
|
||||
gitlab?: string[];
|
||||
bitbucket?: string[];
|
||||
|
||||
@@ -84,6 +84,7 @@ export class ScaffolderClient implements ScaffolderApi {
|
||||
),
|
||||
...this.scmIntegrationsApi.bitbucketCloud.list(),
|
||||
...this.scmIntegrationsApi.bitbucketServer.list(),
|
||||
...this.scmIntegrationsApi.gerrit.list(),
|
||||
...this.scmIntegrationsApi.github.list(),
|
||||
...this.scmIntegrationsApi.gitlab.list(),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2022 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 from 'react';
|
||||
import { GerritRepoPicker } from './GerritRepoPicker';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
|
||||
describe('BitbucketRepoPicker', () => {
|
||||
describe('owner input field', () => {
|
||||
it('calls onChange when the owner input changes', () => {
|
||||
const onChange = jest.fn();
|
||||
const { getAllByRole } = render(
|
||||
<GerritRepoPicker
|
||||
onChange={onChange}
|
||||
rawErrors={[]}
|
||||
state={{ host: 'gerrithost.org' }}
|
||||
/>,
|
||||
);
|
||||
|
||||
const ownerInput = getAllByRole('textbox')[0];
|
||||
|
||||
fireEvent.change(ownerInput, { target: { value: 'test-owner' } });
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({ owner: 'test-owner' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('parent field', () => {
|
||||
it('calls onChange when the parent changes', () => {
|
||||
const onChange = jest.fn();
|
||||
const { getAllByRole } = render(
|
||||
<GerritRepoPicker
|
||||
onChange={onChange}
|
||||
rawErrors={[]}
|
||||
state={{ host: 'gerrithost.org' }}
|
||||
/>,
|
||||
);
|
||||
|
||||
const parentInput = getAllByRole('textbox')[1];
|
||||
|
||||
fireEvent.change(parentInput, { target: { value: 'test-parent' } });
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({ workspace: 'test-parent' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('repoName field', () => {
|
||||
it('calls onChange when the repoName changes', () => {
|
||||
const onChange = jest.fn();
|
||||
const { getAllByRole } = render(
|
||||
<GerritRepoPicker
|
||||
onChange={onChange}
|
||||
rawErrors={[]}
|
||||
state={{ host: 'gerrithost.org' }}
|
||||
/>,
|
||||
);
|
||||
|
||||
const repoNameInput = getAllByRole('textbox')[2];
|
||||
|
||||
fireEvent.change(repoNameInput, { target: { value: 'test-repo' } });
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({ repoName: 'test-repo' });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2022 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 from 'react';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import Input from '@material-ui/core/Input';
|
||||
import InputLabel from '@material-ui/core/InputLabel';
|
||||
import { RepoUrlPickerState } from './types';
|
||||
|
||||
export const GerritRepoPicker = (props: {
|
||||
onChange: (state: RepoUrlPickerState) => void;
|
||||
state: RepoUrlPickerState;
|
||||
rawErrors: string[];
|
||||
}) => {
|
||||
const { onChange, rawErrors, state } = props;
|
||||
const { workspace, repoName, owner } = state;
|
||||
return (
|
||||
<>
|
||||
<FormControl
|
||||
margin="normal"
|
||||
required
|
||||
error={rawErrors?.length > 0 && !workspace}
|
||||
>
|
||||
<InputLabel htmlFor="ownerInput">Owner</InputLabel>
|
||||
<Input
|
||||
id="ownerInput"
|
||||
onChange={e => onChange({ owner: e.target.value })}
|
||||
value={owner}
|
||||
/>
|
||||
<FormHelperText>The owner of the project</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
margin="normal"
|
||||
required
|
||||
error={rawErrors?.length > 0 && !workspace}
|
||||
>
|
||||
<InputLabel htmlFor="parentInput">Parent</InputLabel>
|
||||
<Input
|
||||
id="parentInput"
|
||||
onChange={e => onChange({ workspace: e.target.value })}
|
||||
value={workspace}
|
||||
/>
|
||||
<FormHelperText>
|
||||
The project parent that the repo will belong to
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
margin="normal"
|
||||
required
|
||||
error={rawErrors?.length > 0 && !repoName}
|
||||
>
|
||||
<InputLabel htmlFor="repoNameInput">Repository</InputLabel>
|
||||
<Input
|
||||
id="repoNameInput"
|
||||
onChange={e => onChange({ repoName: e.target.value })}
|
||||
value={repoName}
|
||||
/>
|
||||
<FormHelperText>The name of the repository</FormHelperText>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -23,6 +23,7 @@ import { GithubRepoPicker } from './GithubRepoPicker';
|
||||
import { GitlabRepoPicker } from './GitlabRepoPicker';
|
||||
import { AzureRepoPicker } from './AzureRepoPicker';
|
||||
import { BitbucketRepoPicker } from './BitbucketRepoPicker';
|
||||
import { GerritRepoPicker } from './GerritRepoPicker';
|
||||
import { FieldExtensionComponentProps } from '../../../extensions';
|
||||
import { RepoUrlPickerHost } from './RepoUrlPickerHost';
|
||||
import { parseRepoPickerUrl, serializeRepoPickerUrl } from './utils';
|
||||
@@ -42,6 +43,7 @@ export interface RepoUrlPickerUiOptions {
|
||||
requestUserCredentials?: {
|
||||
secretsKey: string;
|
||||
additionalScopes?: {
|
||||
gerrit?: string[];
|
||||
github?: string[];
|
||||
gitlab?: string[];
|
||||
bitbucket?: string[];
|
||||
@@ -170,6 +172,13 @@ export const RepoUrlPicker = (
|
||||
onChange={updateLocalState}
|
||||
/>
|
||||
)}
|
||||
{hostType === 'gerrit' && (
|
||||
<GerritRepoPicker
|
||||
rawErrors={rawErrors}
|
||||
state={state}
|
||||
onChange={updateLocalState}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user