Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(editor): improve documentation editor using Remirror #6631

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jobs:
- name: Gradle build (and test)
run: |
${{ matrix.command }}
env:
NODE_OPTIONS: "--max-old-space-size=3072"
- uses: actions/upload-artifact@v3
if: always()
with:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/docker-unified.yml
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ jobs:
run: |
./gradlew :datahub-frontend:dist -x test -x yarnTest -x yarnLint --parallel
mv ./datahub-frontend/build/distributions/datahub-frontend-*.zip datahub-frontend.zip
env:
NODE_OPTIONS: "--max-old-space-size=3072"
Comment on lines +317 to +318
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this change necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh it's to resolve the failing build that was caused by FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory. in https://github.com/datahub-project/datahub/actions/runs/3804191769/jobs/6471221050. This line simply increases the memory limit allocated to node. If there are other solutions please feel free to make changes to the PR too!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like this failure is still happening :( do you know if we need to put this type of change elsewhere?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another initiative we've discussed is removing the unneeded dependencies from our package.json - unsure how many things we can remove but if we can shrink our dependencies this may help solve this problem! it's something i'm looking into and will see if it helps here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, the latest build passed though: https://github.com/datahub-project/datahub/actions/runs/3805789729/jobs/6559405831. The only pipeline that's failing is https://github.com/datahub-project/datahub/actions/runs/3805789729/jobs/6559407548 which looks unrelated to the documentation, probably a flaky test (but I can't rereun it :x)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also let me know if you need help looking into shrinking the dependencies 🗡️ !

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah so it looks like it's fixed in the Docker, Build, Scan, Test runs, but i'm still seeing the error in the logs on the general build & test / build job (https://github.com/datahub-project/datahub/actions/runs/3805789734/jobs/6521531814) - you'll have to search for it cuz that's a huge log lol

also it seems that that's timing out after 60 minutes, my hunch is that it's related to this build failing or even that something is taking much longer for some reason.

- name: Build and push
uses: ./.github/actions/docker-custom-build-and-push
with:
Expand Down
10 changes: 10 additions & 0 deletions datahub-web-react/craco.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,14 @@ module.exports = {
},
},
],
jest: {
configure: (jestConfig) => {
jestConfig.transformIgnorePatterns = [
// Ensures that lib0 and y-protocol libraries are transformed through babel as well
'node_modules/(?!(lib0|y-protocols)).+\\.(js|jsx|mjs|cjs|ts|tsx)$',
'^.+\\.module\\.(css|sass|scss)$',
];
return jestConfig;
},
},
};
6 changes: 6 additions & 0 deletions datahub-web-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"@miragejs/graphql": "^0.1.11",
"@monaco-editor/react": "^4.3.1",
"@react-hook/window-size": "^3.0.7",
"@remirror/pm": "^2.0.3",
"@remirror/react": "^2.0.24",
"@remirror/styles": "^2.0.3",
"@testing-library/jest-dom": "^5.11.6",
"@testing-library/react": "^11.2.2",
"@tommoor/remove-markdown": "^0.3.2",
Expand Down Expand Up @@ -53,6 +56,7 @@
"miragejs": "^0.1.41",
"moment-timezone": "^0.5.35",
"monaco-editor": "^0.28.1",
"prosemirror-autocomplete": "^0.4.3",
"query-string": "^6.13.8",
"rc-table": "^7.13.1",
"react": "^17.0.0",
Expand All @@ -69,9 +73,11 @@
"react-syntax-highlighter": "^15.4.4",
"react-visibility-sensor": "^5.1.1",
"reactour": "1.18.7",
"remirror": "^2.0.23",
"sinon": "^11.1.1",
"start-server-and-test": "1.12.2",
"styled-components": "^5.2.1",
"turndown-plugin-gfm": "^1.0.2",
"typescript": "^4.1.3",
"uuid": "^8.3.2",
"virtualizedtableforantd4": "^1.2.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { FetchResult } from '@apollo/client';
import { UpdateDatasetMutation } from '../../../../../../graphql/dataset.generated';
import UpdateDescriptionModal from '../../../../shared/components/legacy/DescriptionModal';
import StripMarkdownText, { removeMarkdown } from '../../../../shared/components/styled/StripMarkdownText';
import MarkdownViewer from '../../../../shared/components/legacy/MarkdownViewer';
import SchemaEditableContext from '../../../../../shared/SchemaEditableContext';
import { useEntityData } from '../../../../shared/EntityContext';
import analytics, { EventType, EntityActionType } from '../../../../../analytics';
import { Editor } from '../../../../shared/tabs/Documentation/components/editor/Editor';

const EditIcon = styled(EditOutlined)`
cursor: pointer;
Expand Down Expand Up @@ -56,12 +56,6 @@ const DescriptionContainer = styled.div`
}
}
`;

const DescriptionText = styled(MarkdownViewer)`
padding-right: 8px;
display: block;
`;

const EditedLabel = styled(Typography.Text)`
position: absolute;
right: -10px;
Expand All @@ -74,6 +68,15 @@ const ReadLessText = styled(Typography.Link)`
margin-right: 4px;
`;

const StyledViewer = styled(Editor)`
padding-right: 8px;
display: block;

.remirror-editor.ProseMirror {
padding: 0;
}
`;

type Props = {
description: string;
original?: string | null;
Expand Down Expand Up @@ -128,7 +131,7 @@ export default function DescriptionField({ description, onUpdate, isEdited = fal
<DescriptionContainer>
{expanded ? (
<>
{!!description && <DescriptionText source={description} />}
{!!description && <StyledViewer content={description} readOnly />}
{!!description && (
<ExpandedActions>
{overLimit && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { Typography, Modal, Button, Form } from 'antd';
import React, { useState } from 'react';
import styled from 'styled-components';
import MDEditor from '@uiw/react-md-editor';

const DescriptionMarkdown = styled(MDEditor.Markdown)`
padding: 4px 10px;
`;
import { Editor } from '../../tabs/Documentation/components/editor/Editor';
import { ANTD_GRAY } from '../../constants';

const FormLabel = styled(Typography.Text)`
font-size: 10px;
font-weight: bold;
`;

const MarkDownHelpLink = styled(Typography.Link)`
position: absolute;
right: 0;
top: -18px;
font-size: 12px;
const StyledEditor = styled(Editor)`
border: 1px solid ${ANTD_GRAY[4.5]};
`;

const StyledViewer = styled(Editor)`
.remirror-editor.ProseMirror {
padding: 0;
}
`;

type Props = {
Expand Down Expand Up @@ -48,36 +48,12 @@ export default function UpdateDescriptionModal({ title, description, original, o
}
>
<Form layout="vertical">
{isAddDesc ? (
<Form.Item>
<MarkDownHelpLink href="https://joplinapp.org/markdown" target="_blank" type="secondary">
markdown supported
</MarkDownHelpLink>
<MDEditor
style={{ fontWeight: 400 }}
value={updatedDesc}
onChange={(v) => setDesc(v || '')}
preview="live"
height={400}
/>
</Form.Item>
) : (
<Form.Item>
<MarkDownHelpLink href="https://joplinapp.org/markdown" target="_blank" type="secondary">
markdown supported
</MarkDownHelpLink>
<MDEditor
style={{ fontWeight: 400 }}
value={updatedDesc}
onChange={(v) => setDesc(v || '')}
preview="live"
height={400}
/>
</Form.Item>
)}
<Form.Item>
<StyledEditor content={updatedDesc} onChange={setDesc} />
</Form.Item>
{!isAddDesc && description && original && (
<Form.Item label={<FormLabel>Original:</FormLabel>}>
<DescriptionMarkdown source={original || ''} />
<StyledViewer content={original || ''} readOnly />
</Form.Item>
)}
</Form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { useLocation } from 'react-router-dom';
import styled from 'styled-components';
import { Button, Divider, Typography } from 'antd';
import { EditOutlined } from '@ant-design/icons';
import MDEditor from '@uiw/react-md-editor';
import DOMPurify from 'dompurify';

import TabToolbar from '../../components/styled/TabToolbar';
import { AddLinkModal } from '../../components/styled/AddLinkModal';
Expand All @@ -16,12 +14,12 @@ import { LinkList } from './components/LinkList';

import { useEntityData, useRefetch, useRouteToTab } from '../../EntityContext';
import { EDITED_DESCRIPTIONS_CACHE_NAME } from '../../utils';
import { Editor } from './components/editor/Editor';

const DocumentationContainer = styled.div`
margin: 0 auto;
margin: 0 32px;
padding: 40px 0;
max-width: calc(100% - 10px);
margin: 0 32px;
`;

interface Props {
Expand All @@ -33,7 +31,6 @@ export const DocumentationTab = ({ properties }: { properties?: Props }) => {
const { urn, entityData } = useEntityData();
const refetch = useRefetch();
const description = entityData?.editableProperties?.description || entityData?.properties?.description || '';
const sanitizedDescription = DOMPurify.sanitize(description);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

even if remirror also handles this, do you think it's a problem to sanitize here as well? just in case anything ever changes with remirror I really want to avoid any unsafe scripts or anything getting into documentation

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same idea with sanitizing below before sending to the BE

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I remembered the double sanitisation did throw out some unwanted syntax in the output. Let me take a look at it again.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay if that's the case I don't think it's a problem as long as we feel good about remirror being able to handle this for us

const links = entityData?.institutionalMemory?.elements || [];
const localStorageDictionary = localStorage.getItem(EDITED_DESCRIPTIONS_CACHE_NAME);

Expand All @@ -53,7 +50,7 @@ export const DocumentationTab = ({ properties }: { properties?: Props }) => {
</>
) : (
<>
{sanitizedDescription || links.length ? (
{description || links.length ? (
<>
<TabToolbar>
<div>
Expand All @@ -66,15 +63,19 @@ export const DocumentationTab = ({ properties }: { properties?: Props }) => {
{!hideLinksButton && <AddLinkModal buttonProps={{ type: 'text' }} refetch={refetch} />}
</div>
</TabToolbar>
<DocumentationContainer>
{sanitizedDescription ? (
<MDEditor.Markdown style={{ fontWeight: 400 }} source={sanitizedDescription} />
<div>
{description ? (
<Editor content={description} readOnly />
) : (
<Typography.Text type="secondary">No documentation added yet.</Typography.Text>
<DocumentationContainer>
<Typography.Text type="secondary">No documentation added yet.</Typography.Text>
</DocumentationContainer>
)}
<Divider />
{!hideLinksButton && <LinkList refetch={refetch} />}
</DocumentationContainer>
<DocumentationContainer>
{!hideLinksButton && <LinkList refetch={refetch} />}
</DocumentationContainer>
</div>
</>
) : (
<EmptyTab tab="documentation">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { htmlToMarkdown } from '../../../../components/editor/extensions/htmlToMarkdown';

const cases = [
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you!

['strike', '<strike>Lorem ipsum</strike>', '~Lorem ipsum~'],
['s', '<s>Lorem ipsum</s>', '~Lorem ipsum~'],
['del', '<del>Lorem ipsum</del>', '~Lorem ipsum~'],
[
'should replace blank nodes in table',
'<table><thead><tr><th>Lorem ipsum</th></tr></thead><tbody><tr><td><p>Lorem</p><p>ipsum</p></td></tr></tbody></table>',
'| Lorem ipsum |\n| --- |\n| Lorem<br />ipsum |',
],
['should replace empty p tags to line breaks', '<p>Lorem</p><p></p><p>ipsum</p>', 'Lorem\n\n&nbsp;\n\nipsum'],
[
'should parse image if it does not have a width attribute',
'<img src="/my-image.png" style="width: 100%; min-width: 50px; object-fit: contain;" alt="my image">',
'![my image](/my-image.png)',
],

[
'should highlight code block with html',
'<pre><code class="language language-html">&lt;<span class="pl-ent">p</span>&gt;Hello world&lt;/<span class="pl-ent">p</span>&gt;</code></pre>',
'```html\n<p>Hello world</p>\n```',
],
[
'should highlight code block with js',
'<pre><code class="lang lang-js">;(<span class="pl-k">function</span> () {})()</code></pre>',
'```js\n;(function () {})()\n```',
],
[
'should highlighted remirror code block',
'<pre><code data-code-block-language="ts">;(<span class="pl-k">function</span> () {})()</code></pre>',
'```ts\n;(function () {})()\n```',
],
[
'should parse without language code block',
'<pre><code>;(<span class="pl-k">function</span> () {})()</code></pre>',
'```\n;(function () {})()\n```',
],
[
'should parse datahub mention',
'<span class="mentions" data-datahub-mention-urn="urn:li:dataset:(urn:li:dataPlatform:hive,SampleHiveDataset,PROD)">@SampleHiveDataset</span>',
'[@SampleHiveDataset](urn:li:dataset:(urn:li:dataPlatform:hive,SampleHiveDataset,PROD))',
],
];

const skipParseCases = [
[
'table if ul or li is present',
'<table><thead><tr><th>Lorem ipsum</th></tr></thead><tbody><tr><td><ul><li>Lorem</li><li>ipsum</li></ul></td></tr></tbody></table>',
],
[
'table if present',
'<table><thead><tr><th>Lorem ipsum</th></tr></thead><tbody><tr><td><ul><li>Lorem</li><li>ipsum</li></ul></td></tr></tbody></table>',
],
[
'image if it has a width attribute',
'<img src="/my-image.png" style="width: 100%; min-width: 50px; object-fit: contain;" alt="my image" width="200">',
],
];

describe('htmlToMarkdown', () => {
it.each(cases)('%s', (_, input, expected) => {
expect(htmlToMarkdown(input)).toBe(expected);
});

it.each(skipParseCases)('should skip parsing %s', (_, input) => {
expect(htmlToMarkdown(input)).toBe(input);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { markdownToHtml } from '../../../../components/editor/extensions/markdownToHtml';

const cases = [
[
'should parse datahub mentions',
'Lorem [@SampleHiveDataset](urn:li:dataset:(urn:li:dataPlatform:hive,SampleHiveDataset,PROD)) ipsum',
'<p>Lorem <span data-datahub-mention-urn="urn:li:dataset:(urn:li:dataPlatform:hive,SampleHiveDataset,PROD)">@SampleHiveDataset</span> ipsum</p>\n',
],
['should not parse github mentions', 'Lorem @githubuser ipsum', '<p>Lorem @githubuser ipsum</p>\n'],
[
'should parse invalid mentions as links',
'Lorem [@Some link](/lorem-ipsum) ipsum',
'<p>Lorem <a href="/lorem-ipsum">@Some link</a> ipsum</p>\n',
],
];

describe('markdownToHtml', () => {
it.each(cases)('%s', (_, input, expected) => {
expect(markdownToHtml(input)).toBe(expected);
});
});
Loading