diff --git a/.changeset/red-turtles-melt.md b/.changeset/red-turtles-melt.md new file mode 100644 index 0000000000..436e69116c --- /dev/null +++ b/.changeset/red-turtles-melt.md @@ -0,0 +1,7 @@ +--- +'@backstage/integration': minor +'@backstage/plugin-techdocs-node': minor +'@backstage/plugin-techdocs-module-addons-contrib': patch +--- + +feat(techdocs): add edit button support for bitbucketServer diff --git a/packages/integration/src/ScmIntegrations.ts b/packages/integration/src/ScmIntegrations.ts index 3e712cc012..6d745dcf1c 100644 --- a/packages/integration/src/ScmIntegrations.ts +++ b/packages/integration/src/ScmIntegrations.ts @@ -113,9 +113,21 @@ export class ScmIntegrations implements ScmIntegrationRegistry { } byUrl(url: string | URL): ScmIntegration | undefined { - return Object.values(this.byType) + let candidates = Object.values(this.byType) .map(i => i.byUrl(url)) - .find(Boolean); + .filter(Boolean); + + // Do not return deprecated integrations if there are other options + if (candidates.length > 1) { + const filteredCandidates = candidates.filter( + x => !(x instanceof BitbucketIntegration), + ); + if (filteredCandidates.length !== 0) { + candidates = filteredCandidates; + } + } + + return candidates[0]; } byHost(host: string): ScmIntegration | undefined { diff --git a/packages/integration/src/bitbucketServer/BitbucketServerIntegration.test.ts b/packages/integration/src/bitbucketServer/BitbucketServerIntegration.test.ts index 24d7935e72..00b5e21316 100644 --- a/packages/integration/src/bitbucketServer/BitbucketServerIntegration.test.ts +++ b/packages/integration/src/bitbucketServer/BitbucketServerIntegration.test.ts @@ -44,31 +44,58 @@ describe('BitbucketServerIntegration', () => { expect(integration.title).toBe('h.com'); }); - it('resolves url line number correctly', () => { + it('resolves url', () => { const integration = new BitbucketServerIntegration({ host: 'h.com', } as any); expect( integration.resolveUrl({ - url: './a.yaml', - base: 'https://h.com/my-owner/my-project/src/master/README.md', - lineNumber: 14, + url: './README.md', + base: 'https://h.com/projects/my-project/repos/my-repo/browse/?at=master', }), - ).toBe('https://h.com/my-owner/my-project/src/master/a.yaml#a.yaml-14'); + ).toBe( + 'https://h.com/projects/my-project/repos/my-repo/browse/README.md?at=master', + ); }); - it('resolve edit URL', () => { + it('resolves url with line number', () => { + const integration = new BitbucketServerIntegration({ + host: 'h.com', + } as any); + + expect( + integration.resolveUrl({ + url: './README.md', + base: 'https://h.com/projects/my-project/repos/my-repo/browse/?at=master', + lineNumber: 14, + }), + ).toBe( + 'https://h.com/projects/my-project/repos/my-repo/browse/README.md?at=master#14', + ); + }); + + it('resolves edit url', () => { const integration = new BitbucketServerIntegration({ host: 'h.com', } as any); expect( integration.resolveEditUrl( - 'https://h.com/my-owner/my-project/src/master/README.md', + 'https://h.com/projects/my-project/repos/my-repo/browse/README.md', ), - ).toBe( - 'https://h.com/my-owner/my-project/src/master/README.md?mode=edit&spa=0&at=master', - ); + ).toBe('https://h.com/projects/my-project/repos/my-repo/browse/README.md'); + }); + + it('resolves edit url with query params', () => { + const integration = new BitbucketServerIntegration({ + host: 'h.com', + } as any); + + expect( + integration.resolveEditUrl( + 'https://h.com/projects/my-project/repos/my-repo/browse/README.md?at=master', + ), + ).toBe('https://h.com/projects/my-project/repos/my-repo/browse/README.md'); }); }); diff --git a/packages/integration/src/bitbucketServer/BitbucketServerIntegration.ts b/packages/integration/src/bitbucketServer/BitbucketServerIntegration.ts index 10055aeddc..01886e16bc 100644 --- a/packages/integration/src/bitbucketServer/BitbucketServerIntegration.ts +++ b/packages/integration/src/bitbucketServer/BitbucketServerIntegration.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import parseGitUrl from 'git-url-parse'; import { basicIntegrations, defaultScmResolveUrl } from '../helpers'; import { ScmIntegration, ScmIntegrationsFactory } from '../types'; import { @@ -63,12 +62,11 @@ export class BitbucketServerIntegration implements ScmIntegration { }): string { const resolved = defaultScmResolveUrl(options); - // Bitbucket Server line numbers use the syntax #example.txt-42, rather than #L42 + // Bitbucket Server line numbers use the syntax #42, rather than #L42 if (options.lineNumber) { const url = new URL(resolved); - const filename = url.pathname.split('/').slice(-1)[0]; - url.hash = `${filename}-${options.lineNumber}`; + url.hash = options.lineNumber.toString(); return url.toString(); } @@ -76,14 +74,11 @@ export class BitbucketServerIntegration implements ScmIntegration { } 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(); + // Bitbucket Server doesn't support deep linking to edit mode, therefore there's nothing to do here. + // We just remove query parameters since they cause issues with TechDocs edit button. + if (url.includes('?')) { + return url.substring(0, url.indexOf('?')); + } + return url; } } diff --git a/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.tsx b/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.tsx index c8caa5ddbc..c81eacb1ee 100644 --- a/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.tsx +++ b/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.tsx @@ -118,7 +118,12 @@ export const ReportIssueAddon = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [selection, mainContent, feedbackContainer]); - if (!selection || !repository) return null; + if ( + !selection || + !repository || + !['github', 'gitlab'].includes(repository.type) + ) + return null; if (!feedbackContainer) { feedbackContainer = document.createElement('div'); diff --git a/plugins/techdocs-node/src/stages/generate/helpers.ts b/plugins/techdocs-node/src/stages/generate/helpers.ts index e1a79ecc19..50db0eb86a 100644 --- a/plugins/techdocs-node/src/stages/generate/helpers.ts +++ b/plugins/techdocs-node/src/stages/generate/helpers.ts @@ -101,9 +101,12 @@ export const getRepoUrlFromLocationAnnotation = ( if (locationType === 'url') { const integration = scmIntegrations.byUrl(target); - // We only support it for github and gitlab for now as the edit_uri + // We only support it for github, gitlab and bitbucketServer for now as the edit_uri // is not properly supported for others yet. - if (integration && ['github', 'gitlab'].includes(integration.type)) { + if ( + integration && + ['github', 'gitlab', 'bitbucketServer'].includes(integration.type) + ) { // handle the case where a user manually writes url:https://github.com/backstage/backstage i.e. without /blob/... const { filepathtype } = gitUrlParse(target); if (filepathtype === '') {