Add resolveEditUrl to integrations

Signed-off-by: Oliver Sand <oliver.sand@sda-se.com>
This commit is contained in:
Oliver Sand
2021-02-25 18:36:59 +01:00
parent 29e98205f1
commit 905cbfc966
12 changed files with 146 additions and 7 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/integration': patch
---
Add `resolveEditUrl` to integrations to resolve a URL that can be used to edit
a file in the web interfaces of an SCM.
@@ -88,4 +88,10 @@ describe('ScmIntegrations', () => {
}),
).toBe('https://absolute.com/path');
});
it('can resolveEditUrl using fallback', () => {
expect(i.resolveEditUrl('http://example.com/x/a.yaml')).toBe(
'http://example.com/x/a.yaml',
);
});
});
@@ -91,4 +91,13 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
return integration.resolveUrl(options);
}
resolveEditUrl(url: string): string {
const integration = this.byUrl(url);
if (!integration) {
return url;
}
return integration.resolveEditUrl(url);
}
}
@@ -99,4 +99,18 @@ describe('AzureIntegration', () => {
).toBe('https://dev.azure.com/organization/project/test');
});
});
it('resolve edit URL', () => {
const integration = new AzureIntegration({ host: 'h.com' } as any);
// TODO: The Azure integration doesn't support resolving an edit URL yet,
// instead we keep the input URL.
expect(
integration.resolveEditUrl(
'https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml',
),
).toBe(
'https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml',
);
});
});
@@ -15,7 +15,7 @@
*/
import parseGitUrl from 'git-url-parse';
import { basicIntegrations } from '../helpers';
import { basicIntegrations, isValidUrl } from '../helpers';
import { ScmIntegration, ScmIntegrationsFactory } from '../types';
import { AzureIntegrationConfig, readAzureIntegrationConfigs } from './config';
@@ -53,12 +53,8 @@ export class AzureIntegration implements ScmIntegration {
const { url, base } = options;
// If we can parse the url, it is absolute - then return it verbatim
try {
// eslint-disable-next-line no-new
new URL(url);
if (isValidUrl(url)) {
return url;
} catch {
// Ignore intentionally - looks like a relative path
}
const parsed = parseGitUrl(base);
@@ -78,4 +74,10 @@ export class AzureIntegration implements ScmIntegration {
return newUrl.toString();
}
resolveEditUrl(url: string): string {
// TODO: Implement edit URL for Azure, fallback to view url as I don't know
// how azure works.
return url;
}
}
@@ -44,4 +44,16 @@ describe('BitbucketIntegration', () => {
expect(integration.type).toBe('bitbucket');
expect(integration.title).toBe('h.com');
});
it('resolve edit URL', () => {
const integration = new BitbucketIntegration({ host: 'h.com' } as any);
expect(
integration.resolveEditUrl(
'https://bitbucket.org/my-owner/my-project/src/master/README.md',
),
).toBe(
'https://bitbucket.org/my-owner/my-project/src/master/README.md?mode=edit&spa=0&at=master',
);
});
});
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import parseGitUrl from 'git-url-parse';
import { basicIntegrations, defaultScmResolveUrl } from '../helpers';
import { ScmIntegration, ScmIntegrationsFactory } from '../types';
import {
@@ -51,4 +52,16 @@ export class BitbucketIntegration implements ScmIntegration {
resolveUrl(options: { url: string; base: string }): string {
return defaultScmResolveUrl(options);
}
resolveEditUrl(url: string): string {
const urlData = parseGitUrl(url);
const editUrl = new URL(url);
editUrl.searchParams.set('mode', 'edit');
// TODO: Not sure what spa=0 does, at least bitbucket.org doesn't support it
// but this is taken over from the initial implementation.
editUrl.searchParams.set('spa', '0');
editUrl.searchParams.set('at', urlData.ref);
return editUrl.toString();
}
}
@@ -49,4 +49,34 @@ describe('GitHubIntegration', () => {
expect(integration.title).toBe('h.com');
expect(integration.config.host).toBe('h.com');
});
it('resolveUrl', () => {
const integration = new GitHubIntegration({ host: 'h.com' });
expect(
integration.resolveUrl({
url: '../a.yaml',
base:
'https://github.com/backstage/backstage/blob/master/test/README.md',
}),
).toBe('https://github.com/backstage/backstage/tree/master/a.yaml');
expect(
integration.resolveUrl({
url: './',
base:
'https://github.com/backstage/backstage/blob/master/test/README.md',
}),
).toBe('https://github.com/backstage/backstage/tree/master/test/');
});
it('resolve edit URL', () => {
const integration = new GitHubIntegration({ host: 'h.com' });
expect(
integration.resolveEditUrl(
'https://github.com/backstage/backstage/blob/master/README.md',
),
).toBe('https://github.com/backstage/backstage/edit/master/README.md');
});
});
@@ -47,6 +47,13 @@ export class GitHubIntegration implements ScmIntegration {
}
resolveUrl(options: { url: string; base: string }): string {
return defaultScmResolveUrl(options);
// GitHub uses blob URLs for files and tree urls for directory listings. But
// there is a redirect from tree to blob for files, so we can always return
// tree urls here.
return defaultScmResolveUrl(options).replace('/blob/', '/tree/');
}
resolveEditUrl(url: string): string {
return url.replace('/blob/', '/edit/');
}
}
@@ -43,4 +43,14 @@ describe('GitLabIntegration', () => {
expect(integration.type).toBe('gitlab');
expect(integration.title).toBe('h.com');
});
it('resolve edit URL', () => {
const integration = new GitLabIntegration({ host: 'h.com' } as any);
expect(
integration.resolveEditUrl(
'https://gitlab.com/my-org/my-project/-/blob/develop/README.md',
),
).toBe('https://gitlab.com/my-org/my-project/-/edit/develop/README.md');
});
});
@@ -49,4 +49,8 @@ export class GitLabIntegration implements ScmIntegration {
resolveUrl(options: { url: string; base: string }): string {
return defaultScmResolveUrl(options);
}
resolveEditUrl(url: string): string {
return url.replace('/blob/', '/edit/');
}
}
+26
View File
@@ -51,6 +51,19 @@ export interface ScmIntegration {
* @param options.base The base URL onto which this resolution happens
*/
resolveUrl(options: { url: string; base: string }): string;
/**
* Resolves the edit URL for a file within the SCM system.
*
* Most SCM systems have a web interface that allows viewing and editing files
* in the repository. The returned URL directly jumps into the edit mode for
* the file.
* If this is not possible, the integration can fall back to a URL to view
* the file in the web interface.
*
* @param url The absolute URL to the file that should be edited.
*/
resolveEditUrl(url: string): string;
}
/**
@@ -103,6 +116,19 @@ export interface ScmIntegrationRegistry
* @param options.base The base URL onto which this resolution happens
*/
resolveUrl(options: { url: string; base: string }): string;
/**
* Resolves the edit URL for a file within the SCM system.
*
* Most SCM systems have a web interface that allows viewing and editing files
* in the repository. The returned URL directly jumps into the edit mode for
* the file.
* If this is not possible, the integration can fall back to a URL to view
* the file in the web interface.
*
* @param url The absolute URL to the file that should be edited.
*/
resolveEditUrl(url: string): string;
}
export type ScmIntegrationsFactory<T extends ScmIntegration> = (options: {