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

Add generic and array symbols to erDiagram #3335

Merged
merged 4 commits into from
Aug 23, 2022
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
15 changes: 15 additions & 0 deletions cypress/integration/rendering/erDiagram.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,21 @@ describe('Entity Relationship Diagram', () => {
cy.get('svg');
});

it.only('should render entities with generic and array attributes', () => {
renderGraph(
`
erDiagram
BOOK {
string title
string[] authors
type~T~ type
}
`,
{ logLevel: 1 }
);
cy.get('svg');
});

it('should render entities and attributes with big and small entity names', () => {
renderGraph(
`
Expand Down
24 changes: 1 addition & 23 deletions src/diagrams/class/svgDraw.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { line, curveBasis } from 'd3';
import utils from '../../utils';
import { log } from '../../logger';
import { parseGenericTypes } from '../common/common';

let edgeCount = 0;
export const drawEdge = function (elem, path, relation, conf, diagObj) {
Expand Down Expand Up @@ -412,29 +413,6 @@ const addTspan = function (textEl, txt, isFirst, conf) {
}
};

/**
* Makes generics in typescript syntax
*
* @example <caption>Array of array of strings in typescript syntax</caption>
* // returns "Array<Array<string>>"
* parseGenericTypes('Array~Array~string~~');
*
* @param {string} text The text to convert
* @returns {string} The converted string
*/
const parseGenericTypes = function (text) {
let cleanedText = text;

if (text.indexOf('~') != -1) {
cleanedText = cleanedText.replace('~', '<');
cleanedText = cleanedText.replace('~', '>');

return parseGenericTypes(cleanedText);
} else {
return cleanedText;
}
};

/**
* Gives the styles for a classifier
*
Expand Down
23 changes: 23 additions & 0 deletions src/diagrams/common/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,29 @@ const getUrl = (useAbsolute) => {
*/
export const evaluate = (val) => (val === 'false' || val === false ? false : true);

/**
* Makes generics in typescript syntax
*
* @example <caption>Array of array of strings in typescript syntax</caption>
* // returns "Array<Array<string>>"
* parseGenericTypes('Array~Array~string~~');
*
* @param {string} text The text to convert
* @returns {string} The converted string
*/
export const parseGenericTypes = function (text) {
FlorianWoelki marked this conversation as resolved.
Show resolved Hide resolved
let cleanedText = text;

if (text.indexOf('~') != -1) {
cleanedText = cleanedText.replace('~', '<');
cleanedText = cleanedText.replace('~', '>');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could this be used to insert tags? Scared of < and > nowdays...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It shouldn't be the case because it gets inserted as a text. I've tried to insert some other styles without luck. The text just gets inserted. But thanks for pointing this out 💯


return parseGenericTypes(cleanedText);
} else {
return cleanedText;
}
};

export default {
getRows,
sanitizeText,
Expand Down
9 changes: 8 additions & 1 deletion src/diagrams/common/common.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { sanitizeText, removeScript, removeEscapes } from './common';
import { sanitizeText, removeScript, removeEscapes, parseGenericTypes } from './common';

describe('when securityLevel is antiscript, all script must be removed', function () {
/**
Expand Down Expand Up @@ -103,3 +103,10 @@ describe('Sanitize text', function () {
expect(result).not.toContain('javascript:alert(1)');
});
});

describe('generic parser', function () {
it('should parse generic types', function () {
const result = parseGenericTypes('test~T~');
expect(result).toEqual('test<T>');
});
});
5 changes: 4 additions & 1 deletion src/diagrams/er/erRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { log } from '../../logger';
import erMarkers from './erMarkers';
import { configureSvgSize } from '../../utils';
import addSVGAccessibilityFields from '../../accessibility';
import { parseGenericTypes } from '../common/common';

let conf = {};

Expand Down Expand Up @@ -63,6 +64,8 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
const attrPrefix = `${entityTextNode.node().id}-attr-${attrNum}`;
let nodeHeight = 0;

const attributeType = parseGenericTypes(item.attributeType);

// Add a text node for the attribute type
const typeNode = groupNode
.append('text')
Expand All @@ -76,7 +79,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
'style',
'font-family: ' + getConfig().fontFamily + '; font-size: ' + attrFontSize + 'px'
)
.text(item.attributeType);
.text(attributeType);

// Add a text node for the attribute name
const nameNode = groupNode
Expand Down
3 changes: 2 additions & 1 deletion src/diagrams/er/parser/erDiagram.jison
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
"{" { this.begin("block"); return 'BLOCK_START'; }
<block>\s+ /* skip whitespace in block */
<block>\b((?:PK)|(?:FK))\b return 'ATTRIBUTE_KEY'
<block>[A-Za-z][A-Za-z0-9\-_]* return 'ATTRIBUTE_WORD'
<block>(.*?)[~](.*?)*[~] return 'ATTRIBUTE_WORD';
<block>[A-Za-z][A-Za-z0-9\-_\[\]]* return 'ATTRIBUTE_WORD'
<block>\"[^"]*\" return 'COMMENT';
<block>[\n]+ /* nothing */
<block>"}" { this.popState(); return 'BLOCK_STOP'; }
Expand Down
25 changes: 25 additions & 0 deletions src/diagrams/er/parser/erDiagram.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,31 @@ describe('when parsing ER diagram it...', function () {
expect(entities[entity].attributes.length).toBe(3);
});

it('should allow an entity with attribute that has a generic type', function () {
const entity = 'BOOK';
const attribute1 = 'type~T~ type';
const attribute2 = 'option~T~ readable "comment"';
const attribute3 = 'string id PK';

erDiagram.parser.parse(
`erDiagram\n${entity} {\n${attribute1}\n${attribute2}\n${attribute3}\n}`
);
const entities = erDb.getEntities();
expect(Object.keys(entities).length).toBe(1);
expect(entities[entity].attributes.length).toBe(3);
});

it('should allow an entity with attribute that is an array', function () {
const entity = 'BOOK';
const attribute1 = 'string[] readers FK "comment"';
const attribute2 = 'string[] authors FK';

erDiagram.parser.parse(`erDiagram\n${entity} {\n${attribute1}\n${attribute2}\n}`);
const entities = erDb.getEntities();
expect(Object.keys(entities).length).toBe(1);
expect(entities[entity].attributes.length).toBe(2);
});

it('should allow an entity with multiple attributes to be defined', function () {
const entity = 'BOOK';
const attribute1 = 'string title';
Expand Down