mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-02 02:26:00 +00:00
Feat : show diff between the two versions in versioning history. (#1107)
* Feat : show diff of two version in versioning history. * Style: changed text and background color for diff. * removed previous version dependency * added support for description diff. * minor fix * minor fix * added support for tags diff. * fixed markdown parsing issue. * minor change for entityTable * reverting the entitytable changes. * markdown parsing fixed * added support for column changed diff. * fixed column name column style * fixed versioning summary formatting. * Refactored :EntityversionPage Code * addressing review comments. * eslint-disabled check
This commit is contained in:
parent
6c9202db3d
commit
15ab8a4901
@ -7058,6 +7058,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
|
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
|
||||||
},
|
},
|
||||||
|
"diff": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w=="
|
||||||
|
},
|
||||||
"diff-match-patch": {
|
"diff-match-patch": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
"codemirror": "^5.62.3",
|
"codemirror": "^5.62.3",
|
||||||
"cookie-storage": "^6.1.0",
|
"cookie-storage": "^6.1.0",
|
||||||
"core-js": "^3.10.1",
|
"core-js": "^3.10.1",
|
||||||
|
"diff": "^5.0.0",
|
||||||
"draft-js": "^0.11.7",
|
"draft-js": "^0.11.7",
|
||||||
"eslint": "^6.6.0",
|
"eslint": "^6.6.0",
|
||||||
"eslint-config-react-app": "^5.2.1",
|
"eslint-config-react-app": "^5.2.1",
|
||||||
|
|||||||
@ -1,8 +1,21 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import { cloneDeep, isEqual, isUndefined } from 'lodash';
|
||||||
import { getTeamDetailsPath } from '../../constants/constants';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { ColumnJoins } from '../../generated/entity/data/table';
|
import {
|
||||||
|
ChangeDescription,
|
||||||
|
Column,
|
||||||
|
ColumnJoins,
|
||||||
|
Table,
|
||||||
|
} from '../../generated/entity/data/table';
|
||||||
|
import { TagLabel } from '../../generated/type/tagLabel';
|
||||||
import { getPartialNameFromFQN } from '../../utils/CommonUtils';
|
import { getPartialNameFromFQN } from '../../utils/CommonUtils';
|
||||||
|
import {
|
||||||
|
getDescriptionDiff,
|
||||||
|
getDiffByFieldName,
|
||||||
|
getDiffValue,
|
||||||
|
getTagsDiff,
|
||||||
|
} from '../../utils/EntityVersionUtils';
|
||||||
|
import { getOwnerFromId } from '../../utils/TableUtils';
|
||||||
import { getTableTags } from '../../utils/TagsUtils';
|
import { getTableTags } from '../../utils/TagsUtils';
|
||||||
import Description from '../common/description/Description';
|
import Description from '../common/description/Description';
|
||||||
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
|
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
|
||||||
@ -25,19 +38,262 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
|
|||||||
backHandler,
|
backHandler,
|
||||||
versionHandler,
|
versionHandler,
|
||||||
}: DatasetVersionProp) => {
|
}: DatasetVersionProp) => {
|
||||||
const extraInfo = [
|
const [changeDescription, setChangeDescription] = useState<ChangeDescription>(
|
||||||
{
|
currentVersionData.changeDescription as ChangeDescription
|
||||||
key: 'Owner',
|
);
|
||||||
value:
|
|
||||||
owner?.type === 'team'
|
const getChangeColName = (name: string | undefined) => {
|
||||||
? getTeamDetailsPath(owner?.name || '')
|
return name?.split('.')?.slice(-2, -1)[0];
|
||||||
: owner?.name || '',
|
};
|
||||||
placeholderText: owner?.displayName || '',
|
|
||||||
isLink: owner?.type === 'team',
|
const isEndsWithField = (name: string | undefined, checkWith: string) => {
|
||||||
openInNewTab: false,
|
return name?.endsWith(checkWith);
|
||||||
},
|
};
|
||||||
{ key: 'Tier', value: tier ? tier.split('.')[1] : '' },
|
|
||||||
];
|
const getExtraInfo = () => {
|
||||||
|
const ownerDiff = getDiffByFieldName('owner', changeDescription);
|
||||||
|
|
||||||
|
const oldOwner = getOwnerFromId(
|
||||||
|
JSON.parse(
|
||||||
|
ownerDiff?.added?.oldValue ??
|
||||||
|
ownerDiff?.deleted?.oldValue ??
|
||||||
|
ownerDiff?.updated?.oldValue ??
|
||||||
|
'{}'
|
||||||
|
)?.id
|
||||||
|
);
|
||||||
|
const newOwner = getOwnerFromId(
|
||||||
|
JSON.parse(
|
||||||
|
ownerDiff?.added?.newValue ??
|
||||||
|
ownerDiff?.deleted?.newValue ??
|
||||||
|
ownerDiff?.updated?.newValue ??
|
||||||
|
'{}'
|
||||||
|
)?.id
|
||||||
|
);
|
||||||
|
const ownerPlaceHolder = owner?.name ?? owner?.displayName ?? '';
|
||||||
|
|
||||||
|
const tagsDiff = getDiffByFieldName('tags', changeDescription, true);
|
||||||
|
const newTier = [
|
||||||
|
...JSON.parse(
|
||||||
|
tagsDiff?.added?.newValue ??
|
||||||
|
tagsDiff?.deleted?.newValue ??
|
||||||
|
tagsDiff?.updated?.newValue ??
|
||||||
|
'[]'
|
||||||
|
),
|
||||||
|
].find((t) => (t?.tagFQN as string).startsWith('Tier'));
|
||||||
|
|
||||||
|
const oldTier = [
|
||||||
|
...JSON.parse(
|
||||||
|
tagsDiff?.added?.oldValue ??
|
||||||
|
tagsDiff?.deleted?.oldValue ??
|
||||||
|
tagsDiff?.updated?.oldValue ??
|
||||||
|
'[]'
|
||||||
|
),
|
||||||
|
].find((t) => (t?.tagFQN as string).startsWith('Tier'));
|
||||||
|
|
||||||
|
const extraInfo = [
|
||||||
|
{
|
||||||
|
key: 'Owner',
|
||||||
|
value:
|
||||||
|
!isUndefined(ownerDiff.added) ||
|
||||||
|
!isUndefined(ownerDiff.deleted) ||
|
||||||
|
!isUndefined(ownerDiff.updated)
|
||||||
|
? getDiffValue(
|
||||||
|
oldOwner?.displayName || oldOwner?.name || '',
|
||||||
|
newOwner?.displayName || newOwner?.name || ''
|
||||||
|
)
|
||||||
|
: ownerPlaceHolder
|
||||||
|
? getDiffValue(ownerPlaceHolder, ownerPlaceHolder)
|
||||||
|
: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Tier',
|
||||||
|
value:
|
||||||
|
!isUndefined(newTier) || !isUndefined(oldTier)
|
||||||
|
? getDiffValue(
|
||||||
|
oldTier?.tagFQN?.split('.')[1] || '',
|
||||||
|
newTier?.tagFQN?.split('.')[1] || ''
|
||||||
|
)
|
||||||
|
: tier
|
||||||
|
? tier.split('.')[1]
|
||||||
|
: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return extraInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTableDescription = () => {
|
||||||
|
const descriptionDiff = getDiffByFieldName(
|
||||||
|
'description',
|
||||||
|
changeDescription
|
||||||
|
);
|
||||||
|
const oldDescription =
|
||||||
|
descriptionDiff?.added?.oldValue ??
|
||||||
|
descriptionDiff?.deleted?.oldValue ??
|
||||||
|
descriptionDiff?.updated?.oldValue;
|
||||||
|
const newDescription =
|
||||||
|
descriptionDiff?.added?.newValue ??
|
||||||
|
descriptionDiff?.deleted?.newValue ??
|
||||||
|
descriptionDiff?.updated?.newValue;
|
||||||
|
|
||||||
|
return getDescriptionDiff(
|
||||||
|
oldDescription,
|
||||||
|
newDescription,
|
||||||
|
currentVersionData.description
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedColumns = (): Table['columns'] => {
|
||||||
|
const colList = cloneDeep(currentVersionData.columns);
|
||||||
|
const columnsDiff = getDiffByFieldName('columns', changeDescription);
|
||||||
|
const changedColName = getChangeColName(
|
||||||
|
columnsDiff?.added?.name ??
|
||||||
|
columnsDiff?.deleted?.name ??
|
||||||
|
columnsDiff?.updated?.name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
isEndsWithField(
|
||||||
|
columnsDiff?.added?.name ??
|
||||||
|
columnsDiff?.deleted?.name ??
|
||||||
|
columnsDiff?.updated?.name,
|
||||||
|
'description'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const oldDescription =
|
||||||
|
columnsDiff?.added?.oldValue ??
|
||||||
|
columnsDiff?.deleted?.oldValue ??
|
||||||
|
columnsDiff?.updated?.oldValue;
|
||||||
|
const newDescription =
|
||||||
|
columnsDiff?.added?.newValue ??
|
||||||
|
columnsDiff?.deleted?.newValue ??
|
||||||
|
columnsDiff?.updated?.newValue;
|
||||||
|
|
||||||
|
const formatColumnData = (arr: Table['columns']) => {
|
||||||
|
arr?.forEach((i) => {
|
||||||
|
if (isEqual(i.name, changedColName)) {
|
||||||
|
i.description = getDescriptionDiff(
|
||||||
|
oldDescription,
|
||||||
|
newDescription,
|
||||||
|
i.description
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
formatColumnData(i?.children as Table['columns']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
formatColumnData(colList);
|
||||||
|
|
||||||
|
return colList;
|
||||||
|
} else if (
|
||||||
|
isEndsWithField(
|
||||||
|
columnsDiff?.added?.name ??
|
||||||
|
columnsDiff?.deleted?.name ??
|
||||||
|
columnsDiff?.updated?.name,
|
||||||
|
'tags'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const oldTags: Array<TagLabel> = JSON.parse(
|
||||||
|
columnsDiff?.added?.oldValue ??
|
||||||
|
columnsDiff?.deleted?.oldValue ??
|
||||||
|
columnsDiff?.updated?.oldValue ??
|
||||||
|
'[]'
|
||||||
|
);
|
||||||
|
const newTags: Array<TagLabel> = JSON.parse(
|
||||||
|
columnsDiff?.added?.newValue ??
|
||||||
|
columnsDiff?.deleted?.newValue ??
|
||||||
|
columnsDiff?.updated?.newValue ??
|
||||||
|
'[]'
|
||||||
|
);
|
||||||
|
|
||||||
|
const formatColumnData = (arr: Table['columns']) => {
|
||||||
|
arr?.forEach((i) => {
|
||||||
|
if (isEqual(i.name, changedColName)) {
|
||||||
|
const flag: { [x: string]: boolean } = {};
|
||||||
|
const uniqueTags: Array<
|
||||||
|
TagLabel & { added: boolean; removed: boolean }
|
||||||
|
> = [];
|
||||||
|
const tagsDiff = getTagsDiff(oldTags, newTags);
|
||||||
|
[...tagsDiff, ...(i.tags as Array<TagLabel>)].forEach(
|
||||||
|
(elem: TagLabel & { added: boolean; removed: boolean }) => {
|
||||||
|
if (!flag[elem.tagFQN as string]) {
|
||||||
|
flag[elem.tagFQN as string] = true;
|
||||||
|
uniqueTags.push(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
i.tags = uniqueTags;
|
||||||
|
} else {
|
||||||
|
formatColumnData(i?.children as Table['columns']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
formatColumnData(colList);
|
||||||
|
|
||||||
|
return colList;
|
||||||
|
} else {
|
||||||
|
const columnsDiff = getDiffByFieldName(
|
||||||
|
'columns',
|
||||||
|
changeDescription,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
if (columnsDiff.added) {
|
||||||
|
const newCol: Array<Column> = JSON.parse(
|
||||||
|
columnsDiff.added?.newValue ?? '[]'
|
||||||
|
);
|
||||||
|
newCol.forEach((col) => {
|
||||||
|
const formatColumnData = (arr: Table['columns']) => {
|
||||||
|
arr?.forEach((i) => {
|
||||||
|
if (isEqual(i.name, col.name)) {
|
||||||
|
i.tags = col.tags?.map((tag) => ({ ...tag, added: true }));
|
||||||
|
i.description = getDescriptionDiff(
|
||||||
|
undefined,
|
||||||
|
col.description,
|
||||||
|
col.description
|
||||||
|
);
|
||||||
|
i.dataTypeDisplay = getDescriptionDiff(
|
||||||
|
undefined,
|
||||||
|
col.dataTypeDisplay,
|
||||||
|
col.dataTypeDisplay
|
||||||
|
);
|
||||||
|
i.name = getDescriptionDiff(undefined, col.name, col.name);
|
||||||
|
} else {
|
||||||
|
formatColumnData(i?.children as Table['columns']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
formatColumnData(colList);
|
||||||
|
});
|
||||||
|
|
||||||
|
return colList;
|
||||||
|
} else if (columnsDiff.deleted) {
|
||||||
|
const newCol: Array<Column> = JSON.parse(
|
||||||
|
columnsDiff.deleted?.oldValue ?? '[]'
|
||||||
|
);
|
||||||
|
const newColumns = newCol.map((col) => ({
|
||||||
|
...col,
|
||||||
|
tags: col.tags?.map((tag) => ({ ...tag, removed: true })),
|
||||||
|
description: getDescriptionDiff(
|
||||||
|
col.description,
|
||||||
|
undefined,
|
||||||
|
col.description
|
||||||
|
),
|
||||||
|
dataTypeDisplay: getDescriptionDiff(
|
||||||
|
col.dataTypeDisplay,
|
||||||
|
undefined,
|
||||||
|
col.dataTypeDisplay
|
||||||
|
),
|
||||||
|
name: getDescriptionDiff(col.name, undefined, col.name),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [...newColumns, ...colList];
|
||||||
|
} else {
|
||||||
|
return colList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
@ -52,6 +308,12 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setChangeDescription(
|
||||||
|
currentVersionData.changeDescription as ChangeDescription
|
||||||
|
);
|
||||||
|
}, [currentVersionData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<div
|
<div
|
||||||
@ -65,7 +327,7 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
|
|||||||
<EntityPageInfo
|
<EntityPageInfo
|
||||||
isVersionSelected
|
isVersionSelected
|
||||||
entityName={currentVersionData.name}
|
entityName={currentVersionData.name}
|
||||||
extraInfo={extraInfo}
|
extraInfo={getExtraInfo()}
|
||||||
followersList={[]}
|
followersList={[]}
|
||||||
tags={getTableTags(currentVersionData.columns || [])}
|
tags={getTableTags(currentVersionData.columns || [])}
|
||||||
tier={tier || ''}
|
tier={tier || ''}
|
||||||
@ -80,7 +342,7 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
|
|||||||
<div className="tw-col-span-full">
|
<div className="tw-col-span-full">
|
||||||
<Description
|
<Description
|
||||||
isReadOnly
|
isReadOnly
|
||||||
description={currentVersionData.description || ''}
|
description={getTableDescription()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -92,7 +354,7 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
|
|||||||
['column'],
|
['column'],
|
||||||
'.'
|
'.'
|
||||||
)}
|
)}
|
||||||
columns={currentVersionData.columns}
|
columns={updatedColumns()}
|
||||||
joins={currentVersionData.joins as ColumnJoins[]}
|
joins={currentVersionData.joins as ColumnJoins[]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,10 +10,12 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
|
display: none;
|
||||||
border-left: 1px solid #d9ceee;
|
border-left: 1px solid #d9ceee;
|
||||||
transition: transform 0.3s ease-out;
|
transition: transform 0.3s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.side-drawer.open {
|
.side-drawer.open {
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -454,76 +454,109 @@ const EntityTable = ({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{cell.column.id === 'dataTypeDisplay' && (
|
{cell.column.id === 'dataTypeDisplay' && (
|
||||||
<div>
|
<>
|
||||||
{cell.value.length > 25 ? (
|
{isReadOnly ? (
|
||||||
<span>
|
<div className="tw-flex tw-flex-wrap tw-w-60 tw-overflow-x-auto">
|
||||||
<PopOver
|
<RichTextEditorPreviewer
|
||||||
html={
|
markdown={cell.value.toLowerCase()}
|
||||||
<div className="tw-break-words">
|
/>
|
||||||
<RichTextEditorPreviewer
|
</div>
|
||||||
markdown={cell.value.toLowerCase()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
position="bottom"
|
|
||||||
theme="light"
|
|
||||||
trigger="click">
|
|
||||||
<p className="tw-cursor-pointer tw-underline tw-inline-block">
|
|
||||||
<RichTextEditorPreviewer
|
|
||||||
markdown={`${cell.value
|
|
||||||
.slice(0, 20)
|
|
||||||
.toLowerCase()}...`}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</PopOver>
|
|
||||||
</span>
|
|
||||||
) : (
|
) : (
|
||||||
cell.value.toLowerCase()
|
<>
|
||||||
|
{cell.value.length > 25 ? (
|
||||||
|
<span>
|
||||||
|
<PopOver
|
||||||
|
html={
|
||||||
|
<div className="tw-break-words">
|
||||||
|
<RichTextEditorPreviewer
|
||||||
|
markdown={cell.value.toLowerCase()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
position="bottom"
|
||||||
|
theme="light"
|
||||||
|
trigger="click">
|
||||||
|
<div className="tw-cursor-pointer tw-underline tw-inline-block">
|
||||||
|
<RichTextEditorPreviewer
|
||||||
|
markdown={`${cell.value
|
||||||
|
.slice(0, 20)
|
||||||
|
.toLowerCase()}...`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</PopOver>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
cell.value.toLowerCase()
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{cell.column.id === 'tags' && (
|
{cell.column.id === 'tags' && (
|
||||||
<div
|
<>
|
||||||
onClick={() => {
|
{isReadOnly ? (
|
||||||
if (!editColumnTag && !isReadOnly) {
|
<div className="tw-flex tw-flex-wrap">
|
||||||
handleEditColumnTag(row.original, row.id);
|
{cell.value?.map(
|
||||||
}
|
(
|
||||||
}}>
|
tag: TagLabel & {
|
||||||
<NonAdminAction
|
added: boolean;
|
||||||
html={getHtmlForNonAdminAction(Boolean(owner))}
|
removed: boolean;
|
||||||
isOwner={hasEditAccess}
|
},
|
||||||
position="left"
|
i: number
|
||||||
trigger="click">
|
) => (
|
||||||
<TagsContainer
|
<Tags
|
||||||
editable={editColumnTag?.index === row.id}
|
className={classNames(
|
||||||
selectedTags={cell.value || []}
|
{ 'diff-added': tag?.added },
|
||||||
tagList={allTags}
|
{ 'diff-removed': tag?.removed }
|
||||||
onCancel={() => {
|
)}
|
||||||
handleTagSelection();
|
key={i}
|
||||||
}}
|
tag={`#${tag.tagFQN}`}
|
||||||
onSelectionChange={(tags) => {
|
/>
|
||||||
handleTagSelection(tags);
|
|
||||||
}}>
|
|
||||||
{!isReadOnly ? (
|
|
||||||
cell.value.length ? (
|
|
||||||
<button className="tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none">
|
|
||||||
<SVGIcons
|
|
||||||
alt="edit"
|
|
||||||
icon="icon-edit"
|
|
||||||
title="Edit"
|
|
||||||
width="10px"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<span className="tw-opacity-60 group-hover:tw-opacity-100 tw-text-grey-muted group-hover:tw-text-primary">
|
|
||||||
<Tags tag="+ Add tag" type="outlined" />
|
|
||||||
</span>
|
|
||||||
)
|
)
|
||||||
) : null}
|
)}
|
||||||
</TagsContainer>
|
</div>
|
||||||
</NonAdminAction>
|
) : (
|
||||||
</div>
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
if (!editColumnTag) {
|
||||||
|
handleEditColumnTag(row.original, row.id);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<NonAdminAction
|
||||||
|
html={getHtmlForNonAdminAction(Boolean(owner))}
|
||||||
|
isOwner={hasEditAccess}
|
||||||
|
position="left"
|
||||||
|
trigger="click">
|
||||||
|
<TagsContainer
|
||||||
|
editable={editColumnTag?.index === row.id}
|
||||||
|
selectedTags={cell.value || []}
|
||||||
|
tagList={allTags}
|
||||||
|
onCancel={() => {
|
||||||
|
handleTagSelection();
|
||||||
|
}}
|
||||||
|
onSelectionChange={(tags) => {
|
||||||
|
handleTagSelection(tags);
|
||||||
|
}}>
|
||||||
|
{cell.value.length ? (
|
||||||
|
<button className="tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none">
|
||||||
|
<SVGIcons
|
||||||
|
alt="edit"
|
||||||
|
icon="icon-edit"
|
||||||
|
title="Edit"
|
||||||
|
width="10px"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span className="tw-opacity-60 group-hover:tw-opacity-100 tw-text-grey-muted group-hover:tw-text-primary">
|
||||||
|
<Tags tag="+ Add tag" type="outlined" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</TagsContainer>
|
||||||
|
</NonAdminAction>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{cell.column.id === 'description' && (
|
{cell.column.id === 'description' && (
|
||||||
<div>
|
<div>
|
||||||
@ -647,15 +680,23 @@ const EntityTable = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{cell.column.id === 'name' && (
|
{cell.column.id === 'name' && (
|
||||||
<span
|
<>
|
||||||
style={{
|
{isReadOnly ? (
|
||||||
paddingLeft: `${
|
<div className="tw-inline-block">
|
||||||
row.canExpand ? '0px' : `${row.depth * 25}px`
|
<RichTextEditorPreviewer markdown={cell.value} />
|
||||||
}`,
|
</div>
|
||||||
}}>
|
) : (
|
||||||
{getConstraintIcon(row.original.constraint)}
|
<span
|
||||||
{cell.render('Cell')}
|
style={{
|
||||||
</span>
|
paddingLeft: `${
|
||||||
|
row.canExpand ? '0px' : `${row.depth * 25}px`
|
||||||
|
}`,
|
||||||
|
}}>
|
||||||
|
{getConstraintIcon(row.original.constraint)}
|
||||||
|
{cell.render('Cell')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import classNames from 'classnames';
|
|||||||
import { toString } from 'lodash';
|
import { toString } from 'lodash';
|
||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import { EntityHistory } from '../../generated/type/entityHistory';
|
import { EntityHistory } from '../../generated/type/entityHistory';
|
||||||
|
import { getSummary } from '../../utils/EntityVersionUtils';
|
||||||
import './EntityVersionTimeLine.css';
|
import './EntityVersionTimeLine.css';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -55,24 +56,7 @@ const EntityVersionTimeLine: React.FC<Props> = ({
|
|||||||
v{parseFloat(currV?.version).toFixed(1)}
|
v{parseFloat(currV?.version).toFixed(1)}
|
||||||
</p>
|
</p>
|
||||||
<div className="tw-text-xs tw-font-normal">
|
<div className="tw-text-xs tw-font-normal">
|
||||||
{currV?.changeDescription?.fieldsAdded?.length ? (
|
{getSummary(currV?.changeDescription)}
|
||||||
<p>
|
|
||||||
{currV?.changeDescription?.fieldsAdded?.join(',')} has been
|
|
||||||
added
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
{currV?.changeDescription?.fieldsUpdated?.length ? (
|
|
||||||
<p>
|
|
||||||
{currV?.changeDescription?.fieldsUpdated?.join(',')} has
|
|
||||||
been updated
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
{currV?.changeDescription?.fieldsDeleted?.length ? (
|
|
||||||
<p>
|
|
||||||
{currV?.changeDescription?.fieldsDeleted?.join(',')} has
|
|
||||||
been deleted
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
<p className="tw-text-xs tw-italic">
|
<p className="tw-text-xs tw-italic">
|
||||||
<span className="tw-font-normal">{currV?.updatedBy}</span>
|
<span className="tw-font-normal">{currV?.updatedBy}</span>
|
||||||
|
|||||||
@ -127,22 +127,24 @@ const SchemaTab: FunctionComponent<Props> = ({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-sm-12">
|
{columns?.length > 0 ? (
|
||||||
{checkedValue === 'schema' ? (
|
<div className="col-sm-12">
|
||||||
<EntityTable
|
{checkedValue === 'schema' ? (
|
||||||
columnName={columnName}
|
<EntityTable
|
||||||
hasEditAccess={Boolean(hasEditAccess)}
|
columnName={columnName}
|
||||||
isReadOnly={isReadOnly}
|
hasEditAccess={Boolean(hasEditAccess)}
|
||||||
joins={joins}
|
isReadOnly={isReadOnly}
|
||||||
owner={owner}
|
joins={joins}
|
||||||
searchText={lowerCase(searchText)}
|
owner={owner}
|
||||||
tableColumns={columns}
|
searchText={lowerCase(searchText)}
|
||||||
onUpdate={onUpdate}
|
tableColumns={columns}
|
||||||
/>
|
onUpdate={onUpdate}
|
||||||
) : (
|
/>
|
||||||
<SampleDataTable sampleData={getSampleDataWithType()} />
|
) : (
|
||||||
)}
|
<SampleDataTable sampleData={getSampleDataWithType()} />
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { AxiosError, AxiosResponse } from 'axios';
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
import { toString } from 'lodash';
|
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||||
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
|
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import { getDatabase } from '../../axiosAPIs/databaseAPI';
|
import { getDatabase } from '../../axiosAPIs/databaseAPI';
|
||||||
import { getServiceById } from '../../axiosAPIs/serviceAPI';
|
import { getServiceById } from '../../axiosAPIs/serviceAPI';
|
||||||
@ -34,7 +33,7 @@ const EntityVersionPage: FunctionComponent = () => {
|
|||||||
const [currentVersionData, setCurrentVersionData] = useState<Table>(
|
const [currentVersionData, setCurrentVersionData] = useState<Table>(
|
||||||
{} as Table
|
{} as Table
|
||||||
);
|
);
|
||||||
const [latestVersion, setLatestVersion] = useState<string>();
|
|
||||||
const { version, datasetFQN } = useParams() as Record<string, string>;
|
const { version, datasetFQN } = useParams() as Record<string, string>;
|
||||||
const [isLoading, setIsloading] = useState<boolean>(false);
|
const [isLoading, setIsloading] = useState<boolean>(false);
|
||||||
const [versionList, setVersionList] = useState<EntityHistory>(
|
const [versionList, setVersionList] = useState<EntityHistory>(
|
||||||
@ -45,8 +44,6 @@ const EntityVersionPage: FunctionComponent = () => {
|
|||||||
TitleBreadcrumbProps['titleLinks']
|
TitleBreadcrumbProps['titleLinks']
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
const isMounted = useRef(false);
|
|
||||||
|
|
||||||
const backHandler = () => {
|
const backHandler = () => {
|
||||||
history.push(getDatasetDetailsPath(datasetFQN));
|
history.push(getDatasetDetailsPath(datasetFQN));
|
||||||
};
|
};
|
||||||
@ -62,11 +59,10 @@ const EntityVersionPage: FunctionComponent = () => {
|
|||||||
['owner', 'tags', 'database']
|
['owner', 'tags', 'database']
|
||||||
)
|
)
|
||||||
.then((res: AxiosResponse) => {
|
.then((res: AxiosResponse) => {
|
||||||
const { id, version, owner, tags, name, database } = res.data;
|
const { id, owner, tags, name, database } = res.data;
|
||||||
setTier(getTierFromTableTags(tags));
|
setTier(getTierFromTableTags(tags));
|
||||||
setOwner(getOwnerFromId(owner?.id));
|
setOwner(getOwnerFromId(owner?.id));
|
||||||
setCurrentVersionData(res.data);
|
setCurrentVersionData(res.data);
|
||||||
setLatestVersion(version);
|
|
||||||
getDatabase(database.id, 'service').then((resDB: AxiosResponse) => {
|
getDatabase(database.id, 'service').then((resDB: AxiosResponse) => {
|
||||||
getServiceById('databaseServices', resDB.data.service?.id).then(
|
getServiceById('databaseServices', resDB.data.service?.id).then(
|
||||||
(resService: AxiosResponse) => {
|
(resService: AxiosResponse) => {
|
||||||
@ -119,100 +115,67 @@ const EntityVersionPage: FunctionComponent = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchCurrentVersion = () => {
|
const fetchCurrentVersion = () => {
|
||||||
if (toString(latestVersion) === version) {
|
setIsVersionLoading(true);
|
||||||
setIsVersionLoading(true);
|
getTableDetailsByFQN(
|
||||||
getTableDetailsByFQN(
|
getPartialNameFromFQN(datasetFQN, ['service', 'database', 'table'], '.'),
|
||||||
getPartialNameFromFQN(
|
'database'
|
||||||
datasetFQN,
|
)
|
||||||
['service', 'database', 'table'],
|
.then((res: AxiosResponse) => {
|
||||||
'.'
|
const { id, database, name } = res.data;
|
||||||
),
|
getDatabase(database.id, 'service').then((resDB: AxiosResponse) => {
|
||||||
['owner', 'tags']
|
getServiceById('databaseServices', resDB.data.service?.id).then(
|
||||||
)
|
(resService: AxiosResponse) => {
|
||||||
.then((vRes: AxiosResponse) => {
|
setSlashedTableName([
|
||||||
const { owner, tags } = vRes.data;
|
{
|
||||||
setTier(getTierFromTableTags(tags));
|
name: resService.data.name,
|
||||||
setOwner(getOwnerFromId(owner?.id));
|
url: resService.data.name
|
||||||
setCurrentVersionData(vRes.data);
|
? getServiceDetailsPath(
|
||||||
setIsVersionLoading(false);
|
resService.data.name,
|
||||||
})
|
resService.data.serviceType
|
||||||
.catch((err: AxiosError) => {
|
)
|
||||||
const msg = err.message;
|
: '',
|
||||||
showToast({
|
imgSrc: resService.data.serviceType
|
||||||
variant: 'error',
|
? serviceTypeLogo(resService.data.serviceType)
|
||||||
body:
|
: undefined,
|
||||||
msg ?? `Error while fetching ${datasetFQN} version ${version}`,
|
},
|
||||||
});
|
{
|
||||||
|
name: resDB.data.name,
|
||||||
|
url: getDatabaseDetailsPath(resDB.data.fullyQualifiedName),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: name,
|
||||||
|
url: '',
|
||||||
|
activeTitle: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
setIsVersionLoading(true);
|
|
||||||
getTableDetailsByFQN(
|
|
||||||
getPartialNameFromFQN(
|
|
||||||
datasetFQN,
|
|
||||||
['service', 'database', 'table'],
|
|
||||||
'.'
|
|
||||||
),
|
|
||||||
'database'
|
|
||||||
)
|
|
||||||
.then((res: AxiosResponse) => {
|
|
||||||
const { id, database, name } = res.data;
|
|
||||||
getDatabase(database.id, 'service').then((resDB: AxiosResponse) => {
|
|
||||||
getServiceById('databaseServices', resDB.data.service?.id).then(
|
|
||||||
(resService: AxiosResponse) => {
|
|
||||||
setSlashedTableName([
|
|
||||||
{
|
|
||||||
name: resService.data.name,
|
|
||||||
url: resService.data.name
|
|
||||||
? getServiceDetailsPath(
|
|
||||||
resService.data.name,
|
|
||||||
resService.data.serviceType
|
|
||||||
)
|
|
||||||
: '',
|
|
||||||
imgSrc: resService.data.serviceType
|
|
||||||
? serviceTypeLogo(resService.data.serviceType)
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: resDB.data.name,
|
|
||||||
url: getDatabaseDetailsPath(resDB.data.fullyQualifiedName),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: name,
|
|
||||||
url: '',
|
|
||||||
activeTitle: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
getTableVersion(id, version)
|
getTableVersion(id, version)
|
||||||
.then((vRes: AxiosResponse) => {
|
.then((vRes: AxiosResponse) => {
|
||||||
const { owner, tags } = vRes.data;
|
const { owner, tags } = vRes.data;
|
||||||
setTier(getTierFromTableTags(tags));
|
setTier(getTierFromTableTags(tags));
|
||||||
setOwner(getOwnerFromId(owner?.id));
|
setOwner(getOwnerFromId(owner?.id));
|
||||||
setCurrentVersionData(vRes.data);
|
setCurrentVersionData(vRes.data);
|
||||||
setIsVersionLoading(false);
|
setIsVersionLoading(false);
|
||||||
})
|
})
|
||||||
.catch((err: AxiosError) => {
|
.catch((err: AxiosError) => {
|
||||||
const msg = err.message;
|
const msg = err.message;
|
||||||
showToast({
|
showToast({
|
||||||
variant: 'error',
|
variant: 'error',
|
||||||
body:
|
body:
|
||||||
msg ??
|
msg ?? `Error while fetching ${datasetFQN} version ${version}`,
|
||||||
`Error while fetching ${datasetFQN} version ${version}`,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
})
|
|
||||||
.catch((err: AxiosError) => {
|
|
||||||
const msg = err.message;
|
|
||||||
showToast({
|
|
||||||
variant: 'error',
|
|
||||||
body:
|
|
||||||
msg ?? `Error while fetching ${datasetFQN} version ${version}`,
|
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.catch((err: AxiosError) => {
|
||||||
|
const msg = err.message;
|
||||||
|
showToast({
|
||||||
|
variant: 'error',
|
||||||
|
body: msg ?? `Error while fetching ${datasetFQN} version ${version}`,
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -220,15 +183,9 @@ const EntityVersionPage: FunctionComponent = () => {
|
|||||||
}, [datasetFQN]);
|
}, [datasetFQN]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isMounted.current) {
|
fetchCurrentVersion();
|
||||||
fetchCurrentVersion();
|
|
||||||
}
|
|
||||||
}, [version]);
|
}, [version]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
isMounted.current = true;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
|||||||
@ -30,3 +30,4 @@ declare module 'slick-carousel';
|
|||||||
declare module 'react-table';
|
declare module 'react-table';
|
||||||
declare module 'recharts';
|
declare module 'recharts';
|
||||||
declare module 'react-tutorial';
|
declare module 'react-tutorial';
|
||||||
|
declare module 'diff';
|
||||||
|
|||||||
@ -690,3 +690,16 @@ body .public-DraftStyleDefault-ul li::marker {
|
|||||||
body .list-option.rdw-option-active {
|
body .list-option.rdw-option-active {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Diff style */
|
||||||
|
|
||||||
|
.diff-added {
|
||||||
|
background: rgba(0, 131, 118, 0.2);
|
||||||
|
color: #008376;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
.diff-removed {
|
||||||
|
color: #008376;
|
||||||
|
text-decoration: line-through;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,195 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import { diffArrays, diffWordsWithSpace } from 'diff';
|
||||||
|
import { isUndefined } from 'lodash';
|
||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import ReactDOMServer from 'react-dom/server';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
import rehypeRaw from 'rehype-raw';
|
||||||
|
import gfm from 'remark-gfm';
|
||||||
|
import {
|
||||||
|
ChangeDescription,
|
||||||
|
FieldChange,
|
||||||
|
} from '../generated/entity/services/databaseService';
|
||||||
|
import { TagLabel } from '../generated/type/tagLabel';
|
||||||
|
import { isValidJSONString } from './StringsUtils';
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
const parseMarkdown = (
|
||||||
|
content: string,
|
||||||
|
className: string,
|
||||||
|
isNewLine: boolean
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<ReactMarkdown
|
||||||
|
children={content
|
||||||
|
.replaceAll(/</g, '<')
|
||||||
|
.replaceAll(/>/g, '>')
|
||||||
|
.replaceAll('\\', '')}
|
||||||
|
components={{
|
||||||
|
h1: 'p',
|
||||||
|
h2: 'p',
|
||||||
|
h3: 'p',
|
||||||
|
h4: 'p',
|
||||||
|
h5: 'p',
|
||||||
|
h6: 'p',
|
||||||
|
p: ({ node, children, ...props }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isNewLine ? (
|
||||||
|
<p className={className} {...props}>
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<span className={className} {...props}>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ul: ({ node, children, ...props }) => {
|
||||||
|
const { ordered: _ordered, ...rest } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className={className} style={{ marginLeft: '16px' }} {...rest}>
|
||||||
|
{children}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
rehypePlugins={[[rehypeRaw, { allowDangerousHtml: false }]]}
|
||||||
|
remarkPlugins={[gfm]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDiffByFieldName = (
|
||||||
|
name: string,
|
||||||
|
changeDescription: ChangeDescription,
|
||||||
|
exactMatch?: boolean
|
||||||
|
): {
|
||||||
|
added: FieldChange | undefined;
|
||||||
|
deleted: FieldChange | undefined;
|
||||||
|
updated: FieldChange | undefined;
|
||||||
|
} => {
|
||||||
|
const fieldsAdded = changeDescription?.fieldsAdded || [];
|
||||||
|
const fieldsDeleted = changeDescription?.fieldsDeleted || [];
|
||||||
|
const fieldsUpdated = changeDescription?.fieldsUpdated || [];
|
||||||
|
if (exactMatch) {
|
||||||
|
return {
|
||||||
|
added: fieldsAdded.find((ch) => ch.name === name),
|
||||||
|
deleted: fieldsDeleted.find((ch) => ch.name === name),
|
||||||
|
updated: fieldsUpdated.find((ch) => ch.name === name),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
added: fieldsAdded.find((ch) => ch.name?.startsWith(name)),
|
||||||
|
deleted: fieldsDeleted.find((ch) => ch.name?.startsWith(name)),
|
||||||
|
updated: fieldsUpdated.find((ch) => ch.name?.startsWith(name)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDiffValue = (oldValue: string, newValue: string) => {
|
||||||
|
const diff = diffWordsWithSpace(oldValue, newValue);
|
||||||
|
|
||||||
|
return diff.map((part: any, index: any) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
{ 'diff-added': part.added },
|
||||||
|
{ 'diff-removed': part.removed }
|
||||||
|
)}
|
||||||
|
key={index}>
|
||||||
|
{part.value}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDescriptionDiff = (
|
||||||
|
oldDescription: string | undefined,
|
||||||
|
newDescription: string | undefined,
|
||||||
|
latestDescription: string | undefined
|
||||||
|
) => {
|
||||||
|
if (!isUndefined(newDescription) || !isUndefined(oldDescription)) {
|
||||||
|
const diff = diffWordsWithSpace(oldDescription ?? '', newDescription ?? '');
|
||||||
|
|
||||||
|
const result: Array<string> = diff.map((part: any, index: any) => {
|
||||||
|
const classes = classNames(
|
||||||
|
{ 'diff-added': part.added },
|
||||||
|
{ 'diff-removed': part.removed }
|
||||||
|
);
|
||||||
|
|
||||||
|
return ReactDOMServer.renderToString(
|
||||||
|
<span key={index}>
|
||||||
|
{parseMarkdown(
|
||||||
|
part.value,
|
||||||
|
classes,
|
||||||
|
part.value?.startsWith('\n\n') || part.value?.includes('\n\n')
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.join('');
|
||||||
|
} else {
|
||||||
|
return latestDescription || '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTagsDiff = (
|
||||||
|
oldTagList: Array<TagLabel>,
|
||||||
|
newTagList: Array<TagLabel>
|
||||||
|
) => {
|
||||||
|
const tagDiff = diffArrays(oldTagList, newTagList);
|
||||||
|
const result = tagDiff
|
||||||
|
|
||||||
|
.map((part: any) =>
|
||||||
|
(part.value as Array<TagLabel>).map((tag) => ({
|
||||||
|
...tag,
|
||||||
|
added: part.added,
|
||||||
|
removed: part.removed,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
?.flat(Infinity);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const summaryFormatter = (v: FieldChange) => {
|
||||||
|
const value = JSON.parse(
|
||||||
|
isValidJSONString(v?.newValue)
|
||||||
|
? v?.newValue
|
||||||
|
: isValidJSONString(v?.oldValue)
|
||||||
|
? v?.oldValue
|
||||||
|
: '{}'
|
||||||
|
);
|
||||||
|
if (v.name === 'columns') {
|
||||||
|
return `columns ${value?.map((val: any) => val?.name).join(',')}`;
|
||||||
|
} else if (v.name === 'tags' || v.name?.endsWith('tags')) {
|
||||||
|
return `tags ${value?.map((val: any) => val?.tagFQN)?.join(',')}`;
|
||||||
|
} else {
|
||||||
|
return v.name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSummary = (changeDescription: ChangeDescription) => {
|
||||||
|
const fieldsAdded = [...(changeDescription?.fieldsAdded || [])];
|
||||||
|
const fieldsDeleted = [...(changeDescription?.fieldsDeleted || [])];
|
||||||
|
const fieldsUpdated = [...(changeDescription?.fieldsUpdated || [])];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{fieldsAdded?.length > 0 ? (
|
||||||
|
<p>{fieldsAdded?.map(summaryFormatter)} has been added</p>
|
||||||
|
) : null}
|
||||||
|
{fieldsUpdated?.length ? (
|
||||||
|
<p>{fieldsUpdated?.map(summaryFormatter)} has been updated</p>
|
||||||
|
) : null}
|
||||||
|
{fieldsDeleted?.length ? (
|
||||||
|
<p>{fieldsDeleted?.map(summaryFormatter)} has been deleted</p>
|
||||||
|
) : null}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user