From f94f5658a96e321e59efe335eb2a55d31c567c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20N=C3=A4sman?= Date: Tue, 19 Apr 2022 11:43:28 +0200 Subject: [PATCH 01/14] techdocs-addons-contrib plugin including Report Issue Addon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Anders Näsman --- plugins/techdocs-addons-contrib/.eslintrc.js | 1 + plugins/techdocs-addons-contrib/api-report.md | 33 +++++ plugins/techdocs-addons-contrib/dev/index.tsx | 30 ++++ plugins/techdocs-addons-contrib/package.json | 68 +++++++++ .../src/ReportIssue/IssueLink.test.tsx | 84 +++++++++++ .../src/ReportIssue/IssueLink.tsx | 83 +++++++++++ .../src/ReportIssue/ReportIssue.tsx | 132 ++++++++++++++++++ .../src/ReportIssue/constants.ts | 22 +++ .../src/ReportIssue/hooks.ts | 96 +++++++++++++ .../src/ReportIssue/index.ts | 19 +++ .../src/ReportIssue/types.ts | 38 +++++ plugins/techdocs-addons-contrib/src/index.ts | 28 ++++ .../src/plugin.test.ts | 22 +++ plugins/techdocs-addons-contrib/src/plugin.ts | 46 ++++++ .../techdocs-addons-contrib/src/setupTests.ts | 17 +++ 15 files changed, 719 insertions(+) create mode 100644 plugins/techdocs-addons-contrib/.eslintrc.js create mode 100644 plugins/techdocs-addons-contrib/api-report.md create mode 100644 plugins/techdocs-addons-contrib/dev/index.tsx create mode 100644 plugins/techdocs-addons-contrib/package.json create mode 100644 plugins/techdocs-addons-contrib/src/ReportIssue/IssueLink.test.tsx create mode 100644 plugins/techdocs-addons-contrib/src/ReportIssue/IssueLink.tsx create mode 100644 plugins/techdocs-addons-contrib/src/ReportIssue/ReportIssue.tsx create mode 100644 plugins/techdocs-addons-contrib/src/ReportIssue/constants.ts create mode 100644 plugins/techdocs-addons-contrib/src/ReportIssue/hooks.ts create mode 100644 plugins/techdocs-addons-contrib/src/ReportIssue/index.ts create mode 100644 plugins/techdocs-addons-contrib/src/ReportIssue/types.ts create mode 100644 plugins/techdocs-addons-contrib/src/index.ts create mode 100644 plugins/techdocs-addons-contrib/src/plugin.test.ts create mode 100644 plugins/techdocs-addons-contrib/src/plugin.ts create mode 100644 plugins/techdocs-addons-contrib/src/setupTests.ts diff --git a/plugins/techdocs-addons-contrib/.eslintrc.js b/plugins/techdocs-addons-contrib/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/techdocs-addons-contrib/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/techdocs-addons-contrib/api-report.md b/plugins/techdocs-addons-contrib/api-report.md new file mode 100644 index 0000000000..b02521f2e3 --- /dev/null +++ b/plugins/techdocs-addons-contrib/api-report.md @@ -0,0 +1,33 @@ +## API Report File for "@backstage/plugin-techdocs-addons-contrib" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { ComponentType } from 'react'; + +// @public +export const ReportIssue: ComponentType; + +// @public (undocumented) +export type ReportIssueProps = { + debounceTime?: number; + templateBuilder?: ReportIssueTemplateBuilder; +}; + +// @public (undocumented) +export type ReportIssueTemplate = { + title: string; + body: string; +}; + +// @public (undocumented) +export type ReportIssueTemplateBuilder = ( + selection: Selection, +) => ReportIssueTemplate; + +// @public +export const techdocsAddonsContribPlugin: BackstagePlugin<{}, {}>; +``` diff --git a/plugins/techdocs-addons-contrib/dev/index.tsx b/plugins/techdocs-addons-contrib/dev/index.tsx new file mode 100644 index 0000000000..f220790022 --- /dev/null +++ b/plugins/techdocs-addons-contrib/dev/index.tsx @@ -0,0 +1,30 @@ +/* + * 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 { createDevApp } from '@backstage/dev-utils'; +import { + techdocsAddonsContribPlugin, + TechdocsAddonsContribPage, +} from '../src/plugin'; + +createDevApp() + .registerPlugin(techdocsAddonsContribPlugin) + .addPage({ + element: , + title: 'Root Page', + path: '/techdocs-addons-contrib', + }) + .render(); diff --git a/plugins/techdocs-addons-contrib/package.json b/plugins/techdocs-addons-contrib/package.json new file mode 100644 index 0000000000..e0fd3126c6 --- /dev/null +++ b/plugins/techdocs-addons-contrib/package.json @@ -0,0 +1,68 @@ +{ + "name": "@backstage/plugin-techdocs-addons-contrib", + "description": "Plugin for contributed TechDocs Addons", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "frontend-plugin" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/techdocs-addons-contrib" + }, + "keywords": [ + "backstage", + "techdocs" + ], + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/core-components": "^0.9.3-next.1", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/integration": "^1.0.0", + "@backstage/integration-react": "^1.0.1-next.1", + "@backstage/plugin-techdocs-react": "^0.0.0", + "@backstage/theme": "^0.2.15", + "@material-ui/core": "^4.9.13", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.57", + "git-url-parse": "^11.6.0", + "react-use": "^17.2.4" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.17.0-next.2", + "@backstage/core-app-api": "^1.0.1-next.0", + "@backstage/dev-utils": "^1.0.1-next.0", + "@backstage/test-utils": "^1.0.1-next.1", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", + "@testing-library/user-event": "^14.0.0", + "@types/jest": "^26.0.7", + "@types/node": "^16.11.26", + "msw": "^0.35.0", + "cross-fetch": "^3.1.5" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/techdocs-addons-contrib/src/ReportIssue/IssueLink.test.tsx b/plugins/techdocs-addons-contrib/src/ReportIssue/IssueLink.test.tsx new file mode 100644 index 0000000000..5fff56ecb7 --- /dev/null +++ b/plugins/techdocs-addons-contrib/src/ReportIssue/IssueLink.test.tsx @@ -0,0 +1,84 @@ +/* + * 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 { render, screen, fireEvent, waitFor } from '@testing-library/react'; + +import { analyticsApiRef } from '@backstage/core-plugin-api'; +import { + MockAnalyticsApi, + TestApiProvider, + wrapInTestApp, +} from '@backstage/test-utils'; + +import { IssueLink } from './IssueLink'; + +const defaultProps = { + repository: { + type: 'github', + name: 'backstage', + owner: 'backstage', + protocol: 'https', + resource: 'github.com', + }, + template: { + title: 'Documentation feedback', + body: '## Documentation Feedback 📝', + }, +}; + +describe('FeedbackLink', () => { + const apiSpy = new MockAnalyticsApi(); + + it('Should open new issue tab', () => { + render( + wrapInTestApp( + + + , + ), + ); + + const link = screen.getByText(/Open new Github issue/); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('target', '_blank'); + const encodedTitle = encodeURIComponent(defaultProps.template.title); + const encodedBody = encodeURIComponent(defaultProps.template.body); + expect(link).toHaveAttribute( + 'href', + `https://github.com/backstage/backstage/issues/new?title=${encodedTitle}&body=${encodedBody}`, + ); + }); + + it('Should track click events', async () => { + render( + wrapInTestApp( + + + , + ), + ); + + fireEvent.click(screen.getByText(/Open new Github issue/)); + + await waitFor(() => { + expect(apiSpy.getEvents()[0]).toMatchObject({ + action: 'click', + subject: 'Open new Github issue', + }); + }); + }); +}); diff --git a/plugins/techdocs-addons-contrib/src/ReportIssue/IssueLink.tsx b/plugins/techdocs-addons-contrib/src/ReportIssue/IssueLink.tsx new file mode 100644 index 0000000000..0a1a64218a --- /dev/null +++ b/plugins/techdocs-addons-contrib/src/ReportIssue/IssueLink.tsx @@ -0,0 +1,83 @@ +/* + * 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 { makeStyles } from '@material-ui/core'; +import BugReportIcon from '@material-ui/icons/BugReport'; + +import { Link, GitHubIcon } from '@backstage/core-components'; + +import { ReportIssueTemplate, Repository } from './types'; + +const useStyles = makeStyles(theme => ({ + root: { + display: 'grid', + gridGap: theme.spacing(1), + gridAutoFlow: 'column', + justifyContent: 'center', + alignItems: 'center', + color: theme.palette.common.black, + fontSize: theme.typography.button.fontSize, + }, +})); + +type IssueLinkProps = { + template: ReportIssueTemplate; + repository: Repository; +}; + +const getIcon = ({ type }: Repository) => { + if (type === 'github') { + return GitHubIcon; + } + return BugReportIcon; +}; + +const getName = ({ type }: Repository) => { + if (type === 'github') { + return 'Github'; + } + return 'Gitlab'; +}; + +const getUrl = (repository: Repository, template: ReportIssueTemplate) => { + const { title, body } = template; + const encodedTitle = encodeURIComponent(title); + const encodedBody = encodeURIComponent(body); + const { protocol, resource, owner, name, type } = repository; + const encodedOwner = encodeURIComponent(owner); + const encodedName = encodeURIComponent(name); + + const url = `${protocol}://${resource}/${encodedOwner}/${encodedName}`; + if (type === 'github') { + return `${url}/issues/new?title=${encodedTitle}&body=${encodedBody}`; + } + return `${url}/issues/new?[title]=${encodedTitle}&[body]=${encodedBody}`; +}; + +export const IssueLink = ({ template, repository }: IssueLinkProps) => { + const classes = useStyles(); + + const Icon = getIcon(repository); + const url = getUrl(repository, template); + + return ( + + Open new {getName(repository)} issue + + ); +}; diff --git a/plugins/techdocs-addons-contrib/src/ReportIssue/ReportIssue.tsx b/plugins/techdocs-addons-contrib/src/ReportIssue/ReportIssue.tsx new file mode 100644 index 0000000000..21f0317b3a --- /dev/null +++ b/plugins/techdocs-addons-contrib/src/ReportIssue/ReportIssue.tsx @@ -0,0 +1,132 @@ +/* + * 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, { useState, useEffect } from 'react'; + +import { makeStyles, Portal, Paper } from '@material-ui/core'; + +import { useGitTemplate, useGitRepository } from './hooks'; +import { ReportIssueTemplateBuilder } from './types'; +import { + PAGE_MAIN_CONTENT_SELECTOR, + PAGE_FEEDBACK_LINK_SELECTOR, + ADDON_FEEDBACK_CONTAINER_ID, + ADDON_FEEDBACK_CONTAINER_SELECTOR, +} from './constants'; +import { IssueLink } from './IssueLink'; + +import { + useShadowRootElements, + useShadowRootSelection, +} from '@backstage/plugin-techdocs-react'; + +const useStyles = makeStyles(theme => ({ + root: { + transform: 'translate(-100%, -100%)', + position: 'absolute', + padding: theme.spacing(1), + zIndex: theme.zIndex.tooltip, + background: theme.palette.common.white, + }, +})); + +type Style = { + top: string; + left: string; +}; + +/** + * @public + */ +export type ReportIssueProps = { + debounceTime?: number; + templateBuilder?: ReportIssueTemplateBuilder; +}; + +/** + * Show report issue button when text is highlighted + */ +export const ReportIssueAddon = ({ + debounceTime = 500, + templateBuilder: buildTemplate, +}: ReportIssueProps) => { + const classes = useStyles(); + const [style, setStyle] = useState