fix(techdocs): prevent to throw an error when the SCM provider is not supported (#29154)
* fix(techdocs): prevent to throw error on undefined 'selection'
Error
TypeError
Message
Failed to execute 'containsNode' on 'Selection': parameter 1 is not of type 'Node'.
Stack Trace
TypeError: parameter 1 is not of type 'Node'.
at https://backstage.mpi-internal.com/static/module-backstage.b2517ed5.js:618:1729
at ul (https://backstage.mpi-internal.com/static/module-react-dom.b4fe5cf3.js:16:24313)
at Ct (https://backstage.mpi-internal.com/static/module-react-dom.b4fe5cf3.js:16:42455)
at qs (https://backstage.mpi-internal.com/static/module-react-dom.b4fe5cf3.js:16:34578)
at Ln (https://backstage.mpi-internal.com/static/module-react-dom.b4fe5cf3.js:24:1590)
at MessagePort.Zn (https://backstage.mpi-internal.com/static/module-react-dom.b4fe5cf3.js:24:1980)
Signed-off-by: Andy LADJADJ <andy.ladjadj@adevinta.com>
* doc: add changeset
Signed-off-by: Andy LADJADJ <andy.ladjadj@adevinta.com>
* chore(techinsights-addon): doesn't load the component for unsuported repositories
Signed-off-by: Andy LADJADJ <andy.ladjadj@adevinta.com>
* Update plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssueContent.tsx
Co-authored-by: Thomas Cardonne <thomas.cardonne@adevinta.com>
Signed-off-by: Andy Ladjadj <andy.ladjadj@adevinta.com>
* Update plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssueContent.tsx
Co-authored-by: Thomas Cardonne <thomas.cardonne@adevinta.com>
Signed-off-by: Andy Ladjadj <andy.ladjadj@adevinta.com>
* fix: doesn't test that selection contains feebackContainer
Signed-off-by: Andy LADJADJ <andy.ladjadj@adevinta.com>
* ci: update the changeset
Signed-off-by: Andy LADJADJ <andy.ladjadj@adevinta.com>
---------
Signed-off-by: Andy LADJADJ <andy.ladjadj@adevinta.com>
Signed-off-by: Andy Ladjadj <andy.ladjadj@adevinta.com>
Co-authored-by: Thomas Cardonne <thomas.cardonne@adevinta.com>
This commit is contained in:
@@ -48,10 +48,7 @@ const getIcon = ({ type }: Repository) => {
|
||||
};
|
||||
|
||||
const getName = ({ type }: Repository) => {
|
||||
if (type === 'github') {
|
||||
return 'Github';
|
||||
}
|
||||
return 'Gitlab';
|
||||
return type.charAt(0).toLocaleUpperCase('en-US') + type.slice(1);
|
||||
};
|
||||
|
||||
const getUrl = (repository: Repository, template: ReportIssueTemplate) => {
|
||||
|
||||
@@ -116,7 +116,7 @@ describe('ReportIssue', () => {
|
||||
|
||||
it('renders gitlab link without exploding', async () => {
|
||||
byUrl.mockReturnValue({ type: 'gitlab' });
|
||||
const { shadowRoot, getByText } =
|
||||
const { shadowRoot, getByText, queryByTestId } =
|
||||
await TechDocsAddonTester.buildAddonsInTechDocs([
|
||||
<ReportIssue debounceTime={0} />,
|
||||
])
|
||||
@@ -164,6 +164,8 @@ describe('ReportIssue', () => {
|
||||
fireSelectionChangeEvent(window);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByTestId('report-issue-addon')).toBeInTheDocument();
|
||||
|
||||
const link = getByText('Open new Gitlab issue');
|
||||
expect(link).toHaveAttribute(
|
||||
'href',
|
||||
@@ -180,7 +182,7 @@ describe('ReportIssue', () => {
|
||||
body: options.selection.toString().trim(),
|
||||
});
|
||||
|
||||
const { shadowRoot, getByText } =
|
||||
const { shadowRoot, getByText, queryByTestId } =
|
||||
await TechDocsAddonTester.buildAddonsInTechDocs([
|
||||
<ReportIssue debounceTime={0} templateBuilder={templateBuilder} />,
|
||||
])
|
||||
@@ -228,6 +230,8 @@ describe('ReportIssue', () => {
|
||||
fireSelectionChangeEvent(window);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByTestId('report-issue-addon')).toBeInTheDocument();
|
||||
|
||||
const link = getByText('Open new Gitlab issue');
|
||||
expect(link).toHaveAttribute(
|
||||
'href',
|
||||
@@ -235,4 +239,49 @@ describe('ReportIssue', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render report issue link for unsupported repository type', async () => {
|
||||
byUrl.mockReturnValue({ type: 'gerrit', resource: 'gerrit.example.com' });
|
||||
|
||||
const { shadowRoot, getByText, queryByTestId } =
|
||||
await TechDocsAddonTester.buildAddonsInTechDocs([
|
||||
<ReportIssue debounceTime={0} />,
|
||||
])
|
||||
.withDom(
|
||||
<html lang="en">
|
||||
<head />
|
||||
<body>
|
||||
<div data-md-component="content">
|
||||
<div data-md-component="main">
|
||||
<div className="md-content">
|
||||
<article>
|
||||
<a
|
||||
title="Edit this page"
|
||||
href="https://gerrit.example.com/admin/repos/edit/repo/my/repo/branch/refs/heads/master/file/docs/README.md"
|
||||
>
|
||||
Edit page
|
||||
</a>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>,
|
||||
)
|
||||
.withApis([[scmIntegrationsApiRef, { byUrl }]])
|
||||
.renderWithEffects();
|
||||
|
||||
(shadowRoot as ShadowRoot & Pick<Document, 'getSelection'>).getSelection =
|
||||
() => selection;
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Edit page')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireSelectionChangeEvent(window);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(queryByTestId('report-issue-addon')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,39 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { makeStyles, Portal, Paper } from '@material-ui/core';
|
||||
|
||||
import { useGitTemplate, useGitRepository } from './hooks';
|
||||
import React from 'react';
|
||||
import { 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;
|
||||
};
|
||||
import { ADDON_ISSUE_REPO_TYPES_SUPPORTED } from './constants';
|
||||
import { ReportIssueAddonContent } from './ReportIssueContent';
|
||||
|
||||
/**
|
||||
* Props customizing the <ReportIssue /> Addon.
|
||||
@@ -67,101 +39,24 @@ export type ReportIssueProps = {
|
||||
templateBuilder?: ReportIssueTemplateBuilder;
|
||||
};
|
||||
|
||||
/**
|
||||
* Show report issue button when text is highlighted
|
||||
*/
|
||||
export const ReportIssueAddon = ({
|
||||
debounceTime = 500,
|
||||
templateBuilder: buildTemplate,
|
||||
templateBuilder,
|
||||
}: ReportIssueProps) => {
|
||||
const classes = useStyles();
|
||||
const [style, setStyle] = useState<Style>();
|
||||
|
||||
const repository = useGitRepository();
|
||||
|
||||
const defaultTemplate = useGitTemplate(debounceTime);
|
||||
|
||||
const selection = useShadowRootSelection(debounceTime);
|
||||
|
||||
const [mainContent, feedbackLink] = useShadowRootElements([
|
||||
PAGE_MAIN_CONTENT_SELECTOR,
|
||||
PAGE_FEEDBACK_LINK_SELECTOR,
|
||||
]);
|
||||
|
||||
let [feedbackContainer] = useShadowRootElements([
|
||||
ADDON_FEEDBACK_CONTAINER_SELECTOR,
|
||||
]);
|
||||
|
||||
if (feedbackLink) {
|
||||
feedbackLink.style.display = 'none';
|
||||
}
|
||||
|
||||
// calculates the position of the selected text to be able to set the position of the addon
|
||||
useEffect(() => {
|
||||
if (
|
||||
// todo(backstage/techdocs-core) handle non-repo rendering
|
||||
!repository ||
|
||||
!selection ||
|
||||
!selection.containsNode(mainContent!, true) ||
|
||||
selection?.containsNode(feedbackContainer!, true)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mainContentPosition = mainContent!.getBoundingClientRect();
|
||||
const selectionPosition = selection.getRangeAt(0).getBoundingClientRect();
|
||||
|
||||
// Calculating the distance between the selection's top and the main content's top
|
||||
const distanceFromTop = selectionPosition.top - mainContentPosition.top;
|
||||
const minDistanceFromTop = 50;
|
||||
|
||||
// Defining a base value for 'top'
|
||||
let top = distanceFromTop < minDistanceFromTop ? 101 : distanceFromTop - 16;
|
||||
|
||||
// Checking if the main content is off-screen towards the top
|
||||
if (mainContentPosition.top < 0) {
|
||||
const absMainContentTop = Math.abs(mainContentPosition.top);
|
||||
|
||||
// Adjusting 'top' if the selection is close to the top edge and the main content is off-screen
|
||||
if (distanceFromTop - absMainContentTop < minDistanceFromTop) {
|
||||
top += 89;
|
||||
}
|
||||
}
|
||||
|
||||
setStyle({
|
||||
top: `${top}px`,
|
||||
left: `${selectionPosition.left + selectionPosition.width / 2}px`,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selection, mainContent, feedbackContainer]);
|
||||
|
||||
if (
|
||||
!selection ||
|
||||
!repository ||
|
||||
!['github', 'gitlab'].includes(repository.type)
|
||||
)
|
||||
!ADDON_ISSUE_REPO_TYPES_SUPPORTED.includes(repository.type)
|
||||
) {
|
||||
return null;
|
||||
|
||||
if (!feedbackContainer) {
|
||||
feedbackContainer = document.createElement('div');
|
||||
feedbackContainer.setAttribute('id', ADDON_FEEDBACK_CONTAINER_ID);
|
||||
mainContent!.prepend(feedbackContainer);
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal container={feedbackContainer}>
|
||||
<Paper
|
||||
data-testid="report-issue-addon"
|
||||
className={classes.root}
|
||||
style={style}
|
||||
>
|
||||
<IssueLink
|
||||
repository={repository}
|
||||
template={
|
||||
buildTemplate ? buildTemplate({ selection }) : defaultTemplate
|
||||
}
|
||||
/>
|
||||
</Paper>
|
||||
</Portal>
|
||||
<ReportIssueAddonContent
|
||||
debounceTime={debounceTime}
|
||||
templateBuilder={templateBuilder}
|
||||
repository={repository}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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 } from './hooks';
|
||||
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';
|
||||
import { Repository } from './types';
|
||||
import { ReportIssueProps } from './ReportIssue';
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
// Props customizing the <ReportIssue /> Addon.
|
||||
export type ReportIssueAddonContentProps = ReportIssueProps & {
|
||||
repository: Repository;
|
||||
};
|
||||
|
||||
export const ReportIssueAddonContent = ({
|
||||
debounceTime,
|
||||
templateBuilder,
|
||||
repository,
|
||||
}: ReportIssueAddonContentProps) => {
|
||||
const classes = useStyles();
|
||||
const [style, setStyle] = useState<Style>();
|
||||
const defaultTemplate = useGitTemplate(debounceTime);
|
||||
const selection = useShadowRootSelection(debounceTime);
|
||||
|
||||
const [mainContent, feedbackLink] = useShadowRootElements([
|
||||
PAGE_MAIN_CONTENT_SELECTOR,
|
||||
PAGE_FEEDBACK_LINK_SELECTOR,
|
||||
]);
|
||||
|
||||
let [feedbackContainer] = useShadowRootElements([
|
||||
ADDON_FEEDBACK_CONTAINER_SELECTOR,
|
||||
]);
|
||||
|
||||
if (feedbackLink) {
|
||||
feedbackLink.style.display = 'none';
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!selection ||
|
||||
!selection.containsNode(mainContent, true) ||
|
||||
selection.containsNode(feedbackContainer, true)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mainContentPosition = mainContent.getBoundingClientRect();
|
||||
const selectionPosition = selection.getRangeAt(0).getBoundingClientRect();
|
||||
|
||||
const distanceFromTop = selectionPosition.top - mainContentPosition.top;
|
||||
const minDistanceFromTop = 50;
|
||||
|
||||
let top = distanceFromTop < minDistanceFromTop ? 101 : distanceFromTop - 16;
|
||||
|
||||
if (mainContentPosition.top < 0) {
|
||||
const absMainContentTop = Math.abs(mainContentPosition.top);
|
||||
if (distanceFromTop - absMainContentTop < minDistanceFromTop) {
|
||||
top += 89;
|
||||
}
|
||||
}
|
||||
|
||||
setStyle({
|
||||
top: `${top}px`,
|
||||
left: `${selectionPosition.left + selectionPosition.width / 2}px`,
|
||||
});
|
||||
}, [selection, mainContent, feedbackContainer]);
|
||||
|
||||
if (!feedbackContainer) {
|
||||
feedbackContainer = document.createElement('div');
|
||||
feedbackContainer.setAttribute('id', ADDON_FEEDBACK_CONTAINER_ID);
|
||||
mainContent!.prepend(feedbackContainer);
|
||||
}
|
||||
|
||||
if (!selection || !selection.containsNode(mainContent, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal container={feedbackContainer}>
|
||||
<Paper
|
||||
data-testid="report-issue-addon"
|
||||
className={classes.root}
|
||||
style={style}
|
||||
>
|
||||
<IssueLink
|
||||
repository={repository}
|
||||
template={
|
||||
templateBuilder ? templateBuilder({ selection }) : defaultTemplate
|
||||
}
|
||||
/>
|
||||
</Paper>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
export const ADDON_FEEDBACK_CONTAINER_ID = 'techdocs-report-issue';
|
||||
export const ADDON_FEEDBACK_CONTAINER_SELECTOR = `#${ADDON_FEEDBACK_CONTAINER_ID}`;
|
||||
export const ADDON_ISSUE_REPO_TYPES_SUPPORTED = ['github', 'gitlab'];
|
||||
export const PAGE_EDIT_LINK_SELECTOR = '[title^="Edit this page"]';
|
||||
export const PAGE_FEEDBACK_LINK_SELECTOR = '[title^="Leave feedback for"]';
|
||||
export const PAGE_MAIN_CONTENT_SELECTOR =
|
||||
|
||||
Reference in New Issue
Block a user