diff --git a/.changeset/famous-days-kneel.md b/.changeset/famous-days-kneel.md new file mode 100644 index 0000000000..418a1f6d4e --- /dev/null +++ b/.changeset/famous-days-kneel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +--- + +Added support for Confluence Cloud to the `confluence:transform:markdown` action in addition to the existing Confluence Server support, view the [README](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-confluence-to-markdown) for more details diff --git a/packages/backend/package.json b/packages/backend/package.json index 2d0b615db0..1ee26f4628 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -60,6 +60,7 @@ "@backstage/plugin-proxy-backend": "workspace:^", "@backstage/plugin-rollbar-backend": "workspace:^", "@backstage/plugin-scaffolder-backend": "workspace:^", + "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^", "@backstage/plugin-scaffolder-backend-module-rails": "workspace:^", "@backstage/plugin-search-backend": "workspace:^", "@backstage/plugin-search-backend-module-elasticsearch": "workspace:^", diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts index 821e5c1adf..505a514344 100644 --- a/packages/backend/src/plugins/scaffolder.ts +++ b/packages/backend/src/plugins/scaffolder.ts @@ -15,9 +15,14 @@ */ import { CatalogClient } from '@backstage/catalog-client'; -import { createRouter } from '@backstage/plugin-scaffolder-backend'; +import { + createBuiltinActions, + createRouter, +} from '@backstage/plugin-scaffolder-backend'; import { Router } from 'express'; import type { PluginEnvironment } from '../types'; +import { ScmIntegrations } from '@backstage/integration'; +import { createConfluenceToMarkdownAction } from '@backstage/plugin-scaffolder-backend-module-confluence-to-markdown'; export default async function createPlugin( env: PluginEnvironment, @@ -26,6 +31,24 @@ export default async function createPlugin( discoveryApi: env.discovery, }); + const integrations = ScmIntegrations.fromConfig(env.config); + + const builtInActions = createBuiltinActions({ + integrations, + config: env.config, + catalogClient, + reader: env.reader, + }); + + const actions = [ + ...builtInActions, + createConfluenceToMarkdownAction({ + integrations, + config: env.config, + reader: env.reader, + }), + ]; + return await createRouter({ logger: env.logger, config: env.config, @@ -35,5 +58,6 @@ export default async function createPlugin( identity: env.identity, scheduler: env.scheduler, permissions: env.permissions, + actions, }); } diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md index d9b235d6c1..7dd9bd96b1 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/README.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/README.md @@ -4,16 +4,18 @@ Welcome to the `confluence:transform:markdown` action for the `scaffolder-backen ## Getting started -You need to configure the action in your backend: +The following sections will help you getting started -## From your Backstage root directory +### Configure Action in Backend + +From your Backstage root directory run: ```bash # From your Backstage root directory yarn add --cwd packages/backend @backstage/plugin-scaffolder-backend-module-confluence-to-markdown ``` -Configure the action: +Then configure the action: (you can check the [docs](https://backstage.io/docs/features/software-templates/writing-custom-actions#registering-custom-actions) to see all options): ```typescript @@ -56,15 +58,62 @@ export default async function createPlugin( } ``` -You will also need an access token for authorization with `Read` permissions. You can create a Personal Access Token (PAT) in confluence and add the PAT to your `app-config.yaml` +### Configuration + +There is some configuration that needs to be setup to use this action, these are the base parameters: ```yaml confluence: - baseUrl: ${CONFLUENCE_BASE_URL} - token: ${CONFLUENCE_TOKEN} + baseUrl: 'https://confluence.example.com' + token: '${CONFLUENCE_TOKEN}' ``` -After that you can use the action in your template: +The sections below will go into more details about the Base URL and Auth Methods. + +#### Base URL + +The `baseUrl` for Confluence Cloud should include the product name which is `wiki` by default but can be something else if your Org has changed it. An example `baseUrl` for Confluence Cloud would look like this: `https://example.atlassian.net/wiki` + +If you are using a self-hosted Confluence instance this does not apply to you. Your `baseUrl` would look something like this: `https://confluence.example.com` + +#### Auth Methods + +The default authorization method is `bearer` but `basic` and `userpass` are also supported. Here's how you would configure each of these: + +For `bearer`: + +```yaml +confluence: + baseUrl: 'https://confluence.example.com' + auth: 'bearer' + token: '${CONFLUENCE_TOKEN}' +``` + +For `basic`: + +```yaml +confluence: + baseUrl: 'https://confluence.example.com' + auth: 'basic' + token: '${CONFLUENCE_TOKEN}' + email: 'example@company.org' +``` + +For `userpass` + +```yaml +confluence: + baseUrl: 'https://confluence.example.com' + auth: 'userpass' + username: 'your-username' + password: 'your-password' +``` + +**Note:** For `basic` and `bearer` authorization methods you will need an access token for authorization with `Read` permissions. You can create a Personal Access Token (PAT) in Confluence. The value used should be the raw token as it will be encoded for you by the action. + +### Template Usage + +Here's an example of how you can use the action in your template: ```yaml apiVersion: scaffolder.backstage.io/v1beta3 @@ -72,7 +121,7 @@ kind: Template metadata: name: confluence-to-markdown title: Confluence to Markdown - description: This template converts a single confluence document to Markdown for Techdocs and adds it to a given GitHub repo. + description: This template converts a single Confluence document to Markdown for Techdocs and adds it to a given GitHub repo. tags: - do-not-use - poc @@ -84,10 +133,9 @@ spec: properties: confluenceUrls: type: array - description: Urls for confluence doc to be converted to markdown. In format /display// + description: Urls for Confluence doc to be converted to markdown. In format /display// or /spaces//pages// for Confluence cloud items: type: string - default: confluence url ui:options: addable: true minItems: 1 @@ -110,9 +158,9 @@ spec: repoUrl: ?repo=${{ steps['create-docs'].output.repo }}&owner=${{ steps['create-docs'].output.owner }} branchName: confluence-to-markdown title: Confluence to Markdown - description: PR for converting confluence page to mkdocs + description: PR for converting Confluence page to mkdocs ``` Replace `` with your GitHub URL without `https://`. -You can find a list of all registered actions including their parameters at the /create/actions route in your Backstage application. +You can find a list of all registered actions including their parameters at the `/create/actions` route in your Backstage application. diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts new file mode 100644 index 0000000000..259d34cbf6 --- /dev/null +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/config.d.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2023 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. + */ + +export interface Config { + /** Configuration options for the Confluence to Markdown action */ + confluence?: { + /** + * The base URL for accessing the Confluence API + */ + baseUrl: string; + /** + * Authentication method - basic, bearer, username/password + */ + auth: 'basic' | 'bearer' | 'userpass'; + /** + * Token used for the basic and bearer auth methods + * @visibility secret + */ + token?: string; + /** + * Email used with the token for the basic auth method + * @visibility secret + */ + email?: string; + /** + * Username used with the Username/Password auth method + * @visibility secret + */ + username?: string; + /** + * Password used with the Username/Password auth method + * @visibility secret + */ + password?: string; + }; +} diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml b/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml new file mode 100644 index 0000000000..bd708b915c --- /dev/null +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/docs/sample-template.yaml @@ -0,0 +1,43 @@ +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: confluence-to-markdown + title: Confluence to Markdown + description: This template converts a single Confluence document to Markdown for Techdocs and adds it to a given GitHub repo. + tags: + - do-not-use + - poc +spec: + owner: team-d + type: service + parameters: + - title: Confluence and Github Repo Information + properties: + confluenceUrls: + type: array + description: URLs for Confluence doc to be converted to markdown. In format /display// or /spaces//pages// for Confluence cloud + items: + type: string + ui:options: + addable: true + minItems: 1 + maxItems: 5 + repoUrl: + type: string + title: GitHub URL mkdocs.yaml link + description: The GitHub repo URL to your mkdocs.yaml file. Example + steps: + - id: create-docs + name: Get markdown file created and update markdown.yaml file + action: confluence:transform:markdown + input: + confluenceUrls: ${{ parameters.confluenceUrls }} + repoUrl: ${{ parameters.repoUrl }} + - id: publish + name: Publish PR to GitHub + action: publish:github:pull-request + input: + repoUrl: ?repo=${{ steps['create-docs'].output.repo }}&owner=${{ steps['create-docs'].output.owner }} + branchName: confluence-to-markdown + title: Confluence to Markdown + description: PR for converting Confluence page to mkdocs diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index aa8d0741c9..641017a5d8 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -44,6 +44,8 @@ "msw": "^1.0.0" }, "files": [ - "dist" - ] + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" } diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts index 136bfc521e..6d6a3ac757 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts @@ -155,7 +155,7 @@ describe('confluence:transform:markdown', () => { expect(logger.info).toHaveBeenCalledWith( `Fetching the mkdocs.yml catalog from https://notreal.github.com/space/backstage/mkdocs.yml`, ); - expect(logger.info).toHaveBeenCalledTimes(5); + expect(logger.info).toHaveBeenCalledTimes(6); expect(createWriteStream).toHaveBeenCalledTimes(1); expect(readFile).toHaveBeenCalledTimes(1); expect(writeFile).toHaveBeenCalledTimes(1); @@ -202,7 +202,7 @@ describe('confluence:transform:markdown', () => { expect(logger.info).toHaveBeenCalledWith( `Fetching the mkdocs.yml catalog from https://notreal.github.com/space/backstage/mkdocs.yml`, ); - expect(logger.info).toHaveBeenCalledTimes(5); + expect(logger.info).toHaveBeenCalledTimes(6); expect(createWriteStream).not.toHaveBeenCalled(); expect(readFile).toHaveBeenCalledTimes(1); diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts index 801f8abb28..9b666fd99e 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts @@ -28,6 +28,7 @@ import { fetchConfluence, getAndWriteAttachments, createConfluenceVariables, + getConfluenceConfig, } from './helpers'; /** @@ -57,7 +58,7 @@ export const createConfluenceToMarkdownAction = (options: { type: 'array', title: 'Confluence URL', description: - 'Paste your confluence url. Ensure it follows this format: https://{confluence+base+url}/display/{spacekey}/{page+title}', + 'Paste your Confluence url. Ensure it follows this format: https://{confluence+base+url}/display/{spacekey}/{page+title} or https://{confluence+base+url}/spaces/{spacekey}/pages/1234567/{page+title} for Confluence Cloud', items: { type: 'string', default: 'Confluence URL', @@ -73,6 +74,7 @@ export const createConfluenceToMarkdownAction = (options: { }, }, async handler(ctx) { + const confluenceConfig = getConfluenceConfig(config); const { confluenceUrls, repoUrl } = ctx.input; const parsedRepoUrl = parseGitUrl(repoUrl); const filePathToMkdocs = parsedRepoUrl.filepath.substring( @@ -96,11 +98,12 @@ export const createConfluenceToMarkdownAction = (options: { for (const url of confluenceUrls) { const { spacekey, title, titleWithSpaces } = - await createConfluenceVariables(url); + createConfluenceVariables(url); // This calls confluence to get the page html and page id + ctx.logger.info(`Fetching the Confluence content for ${url}`); const getConfluenceDoc = await fetchConfluence( `/rest/api/content?title=${title}&spaceKey=${spacekey}&expand=body.export_view`, - config, + confluenceConfig, ); if (getConfluenceDoc.results.length === 0) { throw new InputError( @@ -110,7 +113,7 @@ export const createConfluenceToMarkdownAction = (options: { // This gets attachments for the confluence page if they exist const getDocAttachments = await fetchConfluence( `/rest/api/content/${getConfluenceDoc.results[0].id}/child/attachment`, - config, + confluenceConfig, ); if (getDocAttachments.results.length) { @@ -120,7 +123,7 @@ export const createConfluenceToMarkdownAction = (options: { productArray = await getAndWriteAttachments( getDocAttachments, dirPath, - config, + confluenceConfig, filePathToMkdocs, ); } diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts new file mode 100644 index 0000000000..43d3b4b7dc --- /dev/null +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.test.ts @@ -0,0 +1,164 @@ +/* + * Copyright 2023 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 { ConfigReader } from '@backstage/config'; +import { + createConfluenceVariables, + getAuthorizationHeaderValue, + getConfluenceConfig, +} from './helpers'; + +describe('createConfluenceVariables', () => { + it('should return values for Confluence Url', () => { + const url = 'https://confluence.example.com/display/SPACEKEY/Page+Title'; + const { spacekey, title, titleWithSpaces } = createConfluenceVariables(url); + + expect(spacekey).toEqual('SPACEKEY'); + expect(title).toEqual('Page+Title'); + expect(titleWithSpaces).toEqual('Page Title'); + }); + + it('should return values for Confluence Url with prefix', () => { + const url = + 'https://confluence.example.com/prefix/display/PREFIXSPACEKEY/Prefix+Page+Title'; + const { spacekey, title, titleWithSpaces } = createConfluenceVariables(url); + + expect(spacekey).toEqual('PREFIXSPACEKEY'); + expect(title).toEqual('Prefix+Page+Title'); + expect(titleWithSpaces).toEqual('Prefix Page Title'); + }); + + it('should return values for Confluence Cloud Url', () => { + const url = + 'https://example.atlassian.net/wiki/spaces/CLOUDSPACEKEY/pages/1234567/Cloud+Page+Title'; + + const { spacekey, title, titleWithSpaces } = createConfluenceVariables(url); + + expect(spacekey).toEqual('CLOUDSPACEKEY'); + expect(title).toEqual('Cloud+Page+Title'); + expect(titleWithSpaces).toEqual('Cloud Page Title'); + }); +}); + +describe('getConfluenceConfig', () => { + it('should return validate bearer Confluence config', async () => { + const config = new ConfigReader({ + confluence: { + baseUrl: 'https://example.atlassian.net', + auth: 'bearer', + token: 'fake_token', + }, + }); + + const validated = getConfluenceConfig(config); + + expect(validated).toEqual({ + baseUrl: 'https://example.atlassian.net', + auth: 'bearer', + token: 'fake_token', + email: undefined, + username: undefined, + password: undefined, + }); + }); + + it('should return validate basic Confluence config', async () => { + const config = new ConfigReader({ + confluence: { + baseUrl: 'https://example.atlassian.net', + auth: 'basic', + token: 'fake_token', + email: 'example@example.atlassian.net', + }, + }); + + const validated = getConfluenceConfig(config); + + expect(validated).toEqual({ + baseUrl: 'https://example.atlassian.net', + auth: 'basic', + token: 'fake_token', + email: 'example@example.atlassian.net', + username: undefined, + password: undefined, + }); + }); + + it('should return validate userpass Confluence config', async () => { + const config = new ConfigReader({ + confluence: { + baseUrl: 'https://example.atlassian.net', + auth: 'userpass', + username: 'fake_user', + password: 'fake_password', + }, + }); + + const validated = getConfluenceConfig(config); + + expect(validated).toEqual({ + baseUrl: 'https://example.atlassian.net', + auth: 'userpass', + token: undefined, + email: undefined, + username: 'fake_user', + password: 'fake_password', + }); + }); +}); + +describe('getAuthorizationHeaderValue', () => { + it('should return bearer auth header value', async () => { + const config = { + baseUrl: 'https://example.atlassian.net', + auth: 'bearer', + token: 'fake_token', + }; + + const authHeaderValue = getAuthorizationHeaderValue(config); + + expect(authHeaderValue).toEqual('Bearer fake_token'); + }); + + it('should return basic auth header value', async () => { + const config = { + baseUrl: 'https://example.atlassian.net', + auth: 'basic', + token: 'fake_token', + email: 'example@example.atlassian.net', + }; + + const authHeaderValue = getAuthorizationHeaderValue(config); + + // Note: this is fake and just the encoded result + expect(authHeaderValue).toEqual( + 'Basic ZXhhbXBsZUBleGFtcGxlLmF0bGFzc2lhbi5uZXQ6ZmFrZV90b2tlbg==', + ); + }); + + it('should return userpass auth header value', async () => { + const config = { + baseUrl: 'https://example.atlassian.net', + auth: 'userpass', + username: 'fake_user', + password: 'fake_password', + }; + + const authHeaderValue = getAuthorizationHeaderValue(config); + + // Note: this is fake and just the encoded result + expect(authHeaderValue).toEqual('Basic ZmFrZV91c2VyOmZha2VfcGFzc3dvcmQ='); + }); +}); diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts index c741f6f6c0..b2156a1103 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/helpers.ts @@ -43,18 +43,88 @@ export interface Results { results: Result[]; } +export type ConfluenceConfig = { + baseUrl: string; + auth: string; + token?: string; + email?: string; + username?: string; + password?: string; +}; + +export const getConfluenceConfig = (config: Config) => { + const confluenceConfig: ConfluenceConfig = { + baseUrl: config.getString('confluence.baseUrl'), + auth: config.getOptionalString('confluence.auth') ?? 'bearer', + token: config.getOptionalString('confluence.token'), + email: config.getOptionalString('confluence.email'), + username: config.getOptionalString('confluence.username'), + password: config.getOptionalString('confluence.password'), + }; + + if ( + (confluenceConfig.auth === 'basic' || confluenceConfig.auth === 'bearer') && + !confluenceConfig.token + ) { + throw new Error( + `No token provided for the configured '${confluenceConfig.auth}' auth method`, + ); + } + + if (confluenceConfig.auth === 'basic' && !confluenceConfig.email) { + throw new Error( + `No email provided for the configured '${confluenceConfig.auth}' auth method`, + ); + } + + if ( + confluenceConfig.auth === 'userpass' && + (!confluenceConfig.username || !confluenceConfig.password) + ) { + throw new Error( + `No username/password provided for the configured '${confluenceConfig.auth}' auth method`, + ); + } + + return confluenceConfig; +}; + +export const getAuthorizationHeaderValue = (config: ConfluenceConfig) => { + switch (config.auth) { + case 'bearer': + return `Bearer ${config.token}`; + case 'basic': { + const buffer = Buffer.from(`${config.email}:${config.token}`, 'utf8'); + return `Basic ${buffer.toString('base64')}`; + } + case 'userpass': { + const buffer = Buffer.from( + `${config.username}:${config.password}`, + 'utf8', + ); + return `Basic ${buffer.toString('base64')}`; + } + default: + throw new Error(`Unknown auth method '${config.auth}' provided`); + } +}; + export const readFileAsString = async (fileDir: string) => { const content = await fs.readFile(fileDir, 'utf-8'); return content.toString(); }; -export const fetchConfluence = async (relativeUrl: string, config: Config) => { - const baseUrl = config.getString('confluence.baseUrl'); - const token = config.getString('confluence.token'); - const response: Response = await fetch(`${baseUrl}${relativeUrl}`, { +export const fetchConfluence = async ( + relativeUrl: string, + config: ConfluenceConfig, +) => { + const baseUrl = config.baseUrl; + const authHeaderValue = getAuthorizationHeaderValue(config); + const url = `${baseUrl}${relativeUrl}`; + const response: Response = await fetch(url, { method: 'GET', headers: { - Authorization: `Bearer ${token}`, + Authorization: authHeaderValue, }, }); if (!response.ok) { @@ -67,12 +137,12 @@ export const fetchConfluence = async (relativeUrl: string, config: Config) => { export const getAndWriteAttachments = async ( arr: Results, workspace: string, - config: Config, + config: ConfluenceConfig, mkdocsDir: string, ) => { const productArr: string[][] = []; - const baseUrl = config.getString('confluence.baseUrl'); - const token = config.getString('confluence.token'); + const baseUrl = config.baseUrl; + const authHeaderValue = getAuthorizationHeaderValue(config); await Promise.all( await arr.results.map(async (result: Result) => { const downloadLink = result._links.download; @@ -80,11 +150,11 @@ export const getAndWriteAttachments = async ( if (result.metadata.mediaType !== 'application/gliffy+json') { productArr.push([result.title.replace(/ /g, '%20'), downloadTitle]); } - - const res = await fetch(`${baseUrl}${downloadLink}`, { + const url = `${baseUrl}${downloadLink}`; + const res = await fetch(url, { method: 'GET', headers: { - Authorization: `Bearer ${token}`, + Authorization: authHeaderValue, }, }); if (!res.ok) { @@ -111,18 +181,31 @@ export const getAndWriteAttachments = async ( return productArr; }; -export const createConfluenceVariables = async (url: string) => { +export const createConfluenceVariables = (url: string) => { let spacekey: string | undefined = undefined; let title: string | undefined = undefined; let titleWithSpaces: string | undefined = ''; const params = new URL(url); if (params.pathname.split('/')[1] === 'display') { + // https://confluence.example.com/display/SPACEKEY/Page+Title spacekey = params.pathname.split('/')[2]; title = params.pathname.split('/')[3]; titleWithSpaces = title?.replace(/\+/g, ' '); return { spacekey, title, titleWithSpaces }; + } else if (params.pathname.split('/')[2] === 'display') { + // https://confluence.example.com/prefix/display/SPACEKEY/Page+Title + spacekey = params.pathname.split('/')[3]; + title = params.pathname.split('/')[4]; + titleWithSpaces = title?.replace(/\+/g, ' '); + return { spacekey, title, titleWithSpaces }; + } else if (params.pathname.split('/')[2] === 'spaces') { + // https://example.atlassian.net/wiki/spaces/SPACEKEY/pages/1234567/Page+Title + spacekey = params.pathname.split('/')[3]; + title = params.pathname.split('/')[6]; + titleWithSpaces = title?.replace(/\+/g, ' '); + return { spacekey, title, titleWithSpaces }; } throw new InputError( - 'The Url format for Confluence is incorrect. Acceptable format is `/display//`', + 'The Url format for Confluence is incorrect. Acceptable format is `/display//` or `/spaces//pages//` for Confluence cloud', ); }; diff --git a/yarn.lock b/yarn.lock index 473bf53ccb..3eb6a6b53c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8292,7 +8292,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown@workspace:plugins/scaffolder-backend-module-confluence-to-markdown": +"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown@workspace:^, @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@workspace:plugins/scaffolder-backend-module-confluence-to-markdown": version: 0.0.0-use.local resolution: "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown@workspace:plugins/scaffolder-backend-module-confluence-to-markdown" dependencies: @@ -24503,6 +24503,7 @@ __metadata: "@backstage/plugin-proxy-backend": "workspace:^" "@backstage/plugin-rollbar-backend": "workspace:^" "@backstage/plugin-scaffolder-backend": "workspace:^" + "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^" "@backstage/plugin-scaffolder-backend-module-rails": "workspace:^" "@backstage/plugin-search-backend": "workspace:^" "@backstage/plugin-search-backend-module-elasticsearch": "workspace:^"