Merge pull request #28543 from leboncoin/feat-integration-gerrit-edit-mode
feat(integration): add edit url mode for Gerrit integration
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/integration': minor
|
||||
---
|
||||
|
||||
Implement Edit URL feature for Gerrit 3.9+.
|
||||
|
||||
It's possible to disable the edit url by adding the `disableEditUrl: true` config in the Gerrit integration.
|
||||
@@ -11,6 +11,8 @@ or registered with the
|
||||
[catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import)
|
||||
plugin.
|
||||
|
||||
Gerrit 3.9+ supports inline editing via URL. The integration enables this by default, following Gerrit's [official URL pattern](https://gerrit-review.googlesource.com/Documentation/user-inline-edit.html#create_from_url) for inline edits.
|
||||
|
||||
## Configuration
|
||||
|
||||
To use this integration, add at least one Gerrit configuration to your root `app-config.yaml`:
|
||||
@@ -22,6 +24,7 @@ integrations:
|
||||
gitilesBaseUrl: https://gerrit.company.com/gitiles
|
||||
baseUrl: https://gerrit.company.com/gerrit
|
||||
cloneUrl: https://gerrit.company.com/clone
|
||||
disableEditUrl: false
|
||||
username: ${GERRIT_USERNAME}
|
||||
password: ${GERRIT_PASSWORD}
|
||||
```
|
||||
@@ -37,6 +40,7 @@ a structure with up to six elements:
|
||||
address here. This is the address that you would open in a browser.
|
||||
- `cloneUrl` (optional): The base URL for HTTP clones. Will default to `baseUrl` if
|
||||
not set. The address used to clone a repo is the `cloneUrl` plus the repo name.
|
||||
- `disableEditUrl` (optional): Disable the edit mode.
|
||||
- `username` (optional): The Gerrit username to use in API requests. If
|
||||
neither a username nor password are supplied, anonymous access will be used.
|
||||
- `password` (optional): The password or http token for the Gerrit user.
|
||||
|
||||
Vendored
+5
@@ -182,6 +182,11 @@ export interface Config {
|
||||
* @visibility frontend
|
||||
*/
|
||||
cloneUrl?: string;
|
||||
/**
|
||||
* Disable the edit url feature.
|
||||
* @visibility frontend
|
||||
*/
|
||||
disableEditUrl?: boolean;
|
||||
/**
|
||||
* The username to use for authenticated requests.
|
||||
* @visibility secret
|
||||
|
||||
@@ -396,6 +396,7 @@ export type GerritIntegrationConfig = {
|
||||
host: string;
|
||||
baseUrl?: string;
|
||||
cloneUrl?: string;
|
||||
disableEditUrl?: boolean;
|
||||
gitilesBaseUrl: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
|
||||
@@ -47,6 +47,7 @@ describe('GerritIntegration', () => {
|
||||
it('returns the basics', () => {
|
||||
const integration = new GerritIntegration({
|
||||
host: 'gerrit-review.example.com',
|
||||
gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles',
|
||||
} as any);
|
||||
expect(integration.type).toBe('gerrit');
|
||||
expect(integration.title).toBe('gerrit-review.example.com');
|
||||
@@ -70,6 +71,7 @@ describe('GerritIntegration', () => {
|
||||
it('handles line numbers', () => {
|
||||
const integration = new GerritIntegration({
|
||||
host: 'gerrit-review.example.com',
|
||||
gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles',
|
||||
} as any);
|
||||
|
||||
expect(
|
||||
@@ -85,6 +87,7 @@ describe('GerritIntegration', () => {
|
||||
describe('resolves with a relative url', () => {
|
||||
const integration = new GerritIntegration({
|
||||
host: 'gerrit-review.example.com',
|
||||
gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles',
|
||||
} as any);
|
||||
it('works for valid urls pointing to a branch', () => {
|
||||
expect(
|
||||
@@ -135,17 +138,37 @@ describe('GerritIntegration', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('resolve edit URL', () => {
|
||||
it('resolve Url when editUrl is disabled', () => {
|
||||
const integration = new GerritIntegration({
|
||||
host: 'gerrit-review.example.com',
|
||||
disableEditUrl: true,
|
||||
} as any);
|
||||
|
||||
// Resolve edit URLs is not applicable for gerrit. Return the input
|
||||
// url as is.
|
||||
expect(
|
||||
integration.resolveEditUrl(
|
||||
'https://gerrit-review.example.com/catalog-info.yaml',
|
||||
'https://gerrit-review.example.com/gitiles/backstage/backstage/+/refs/heads/master/catalog-info.yaml',
|
||||
),
|
||||
).toBe('https://gerrit-review.example.com/catalog-info.yaml');
|
||||
).toBe(
|
||||
'https://gerrit-review.example.com/gitiles/backstage/backstage/+/refs/heads/master/catalog-info.yaml',
|
||||
);
|
||||
});
|
||||
|
||||
it('resolve edit URL with editUrl', () => {
|
||||
const integration = new GerritIntegration({
|
||||
host: 'gerrit-review.example.com',
|
||||
baseUrl: 'https://gerrit-review.example.com',
|
||||
gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles',
|
||||
disableEditUrl: false,
|
||||
} as any);
|
||||
|
||||
expect(
|
||||
integration.resolveEditUrl(
|
||||
'https://gerrit-review.example.com/gitiles/backstage/backstage/+/refs/heads/master/catalog-info.yaml',
|
||||
),
|
||||
).toBe(
|
||||
'https://gerrit-review.example.com/admin/repos/edit/repo/backstage/backstage/branch/refs/heads/master/file/catalog-info.yaml',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
GerritIntegrationConfig,
|
||||
readGerritIntegrationConfigs,
|
||||
} from './config';
|
||||
import { parseGitilesUrlRef } from './core';
|
||||
import { buildGerritEditUrl, parseGitilesUrlRef } from './core';
|
||||
|
||||
/**
|
||||
* A Gerrit based integration.
|
||||
@@ -75,7 +75,16 @@ export class GerritIntegration implements ScmIntegration {
|
||||
}
|
||||
|
||||
resolveEditUrl(url: string): string {
|
||||
// Not applicable for gerrit.
|
||||
return url;
|
||||
if (this.config.disableEditUrl) {
|
||||
return url;
|
||||
}
|
||||
|
||||
const parsed = parseGitilesUrlRef(this.config, url);
|
||||
return buildGerritEditUrl(
|
||||
this.config,
|
||||
parsed.project,
|
||||
parsed.ref,
|
||||
parsed.path,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ describe('readGerritIntegrationConfig', () => {
|
||||
host: 'a.com',
|
||||
baseUrl: 'https://a.com/api',
|
||||
cloneUrl: 'https:a.com/clone',
|
||||
disableEditUrl: true,
|
||||
gitilesBaseUrl: 'https://a.com/git',
|
||||
username: 'u',
|
||||
password: ' p ',
|
||||
@@ -66,12 +67,32 @@ describe('readGerritIntegrationConfig', () => {
|
||||
host: 'a.com',
|
||||
baseUrl: 'https://a.com/api',
|
||||
cloneUrl: 'https:a.com/clone',
|
||||
disableEditUrl: true,
|
||||
gitilesBaseUrl: 'https://a.com/git',
|
||||
username: 'u',
|
||||
password: 'p',
|
||||
});
|
||||
});
|
||||
|
||||
it('activate Edit Url', () => {
|
||||
const output = readGerritIntegrationConfig(
|
||||
buildConfig({
|
||||
host: 'a.com',
|
||||
disableEditUrl: false,
|
||||
gitilesBaseUrl: 'https://a.com/gerrit/plugins/gitiles',
|
||||
}),
|
||||
);
|
||||
expect(output).toEqual({
|
||||
host: 'a.com',
|
||||
baseUrl: 'https://a.com',
|
||||
cloneUrl: 'https://a.com',
|
||||
disableEditUrl: false,
|
||||
gitilesBaseUrl: 'https://a.com/gerrit/plugins/gitiles',
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('can create a default value if the API base URL is missing', () => {
|
||||
const output = readGerritIntegrationConfig(
|
||||
buildConfig({
|
||||
@@ -156,6 +177,7 @@ describe('readGerritIntegrationConfigs', () => {
|
||||
host: 'b.com',
|
||||
baseUrl: 'https://b.com/api',
|
||||
cloneUrl: 'https://b.com/api',
|
||||
disableEditUrl: undefined,
|
||||
gitilesBaseUrl: 'https://b.com/gerrit/plugins/gitiles',
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
|
||||
@@ -44,6 +44,11 @@ export type GerritIntegrationConfig = {
|
||||
*/
|
||||
cloneUrl?: string;
|
||||
|
||||
/**
|
||||
* Disable the edit url feature for Gerrit version less than 3.9.
|
||||
*/
|
||||
disableEditUrl?: boolean;
|
||||
|
||||
/**
|
||||
* Base url for Gitiles. This is needed for creating a valid
|
||||
* user-friendly url that can be used for browsing the content of the
|
||||
@@ -80,6 +85,7 @@ export function readGerritIntegrationConfig(
|
||||
const host = config.getString('host');
|
||||
let baseUrl = config.getOptionalString('baseUrl');
|
||||
let cloneUrl = config.getOptionalString('cloneUrl');
|
||||
const disableEditUrl = config.getOptionalBoolean('disableEditUrl');
|
||||
let gitilesBaseUrl = config.getString('gitilesBaseUrl');
|
||||
const username = config.getOptionalString('username');
|
||||
const password = config.getOptionalString('password')?.trim();
|
||||
@@ -106,21 +112,19 @@ export function readGerritIntegrationConfig(
|
||||
} else {
|
||||
baseUrl = `https://${host}`;
|
||||
}
|
||||
if (gitilesBaseUrl) {
|
||||
gitilesBaseUrl = trimEnd(gitilesBaseUrl, '/');
|
||||
} else {
|
||||
gitilesBaseUrl = `https://${host}`;
|
||||
}
|
||||
if (cloneUrl) {
|
||||
cloneUrl = trimEnd(cloneUrl, '/');
|
||||
} else {
|
||||
cloneUrl = baseUrl;
|
||||
}
|
||||
|
||||
gitilesBaseUrl = trimEnd(gitilesBaseUrl, '/');
|
||||
|
||||
return {
|
||||
host,
|
||||
baseUrl,
|
||||
cloneUrl,
|
||||
disableEditUrl,
|
||||
gitilesBaseUrl,
|
||||
username,
|
||||
password,
|
||||
|
||||
@@ -208,6 +208,29 @@ export function buildGerritGitilesUrl(
|
||||
}/${project}/+/refs/heads/${branch}/${trimStart(filePath, '/')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 buildGerritEditUrl(
|
||||
config: GerritIntegrationConfig,
|
||||
project: string,
|
||||
branch: string,
|
||||
filePath: string,
|
||||
): string {
|
||||
return `${
|
||||
config.baseUrl
|
||||
}/admin/repos/edit/repo/${project}/branch/refs/heads/${branch}/file/${trimStart(
|
||||
filePath,
|
||||
'/',
|
||||
)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Gerrit Gitiles archive url that targets a specific branch and path
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user