2025-01-29 20:42:01 -05:00
|
|
|
import { SorterResult } from 'antd/lib/table/interface';
|
|
|
|
|
import * as diff from 'diff';
|
|
|
|
|
|
2025-04-16 16:55:38 -07:00
|
|
|
import { SchemaDiffSummary } from '@app/entityV2/dataset/profile/schema/components/SchemaVersionSummary';
|
|
|
|
|
import { KEY_SCHEMA_PREFIX, UNION_TOKEN, VERSION_PREFIX } from '@app/entityV2/dataset/profile/schema/utils/constants';
|
|
|
|
|
import { ExtendedSchemaFields } from '@app/entityV2/dataset/profile/schema/utils/types';
|
|
|
|
|
import { convertTagsForUpdate } from '@app/shared/tags/utils/convertTagsForUpdate';
|
|
|
|
|
|
2025-01-29 20:42:01 -05:00
|
|
|
import {
|
|
|
|
|
EditableSchemaFieldInfo,
|
|
|
|
|
EditableSchemaMetadata,
|
|
|
|
|
EditableSchemaMetadataUpdate,
|
|
|
|
|
PlatformSchema,
|
|
|
|
|
SchemaField,
|
2025-04-16 16:55:38 -07:00
|
|
|
} from '@types';
|
2025-01-29 20:42:01 -05:00
|
|
|
|
|
|
|
|
export function convertEditableSchemaMeta(
|
|
|
|
|
editableSchemaMeta?: Array<EditableSchemaFieldInfo>,
|
|
|
|
|
fields?: Array<SchemaField>,
|
|
|
|
|
): Array<SchemaField> {
|
|
|
|
|
const updatedFields = [...(fields || [])] as Array<SchemaField>;
|
|
|
|
|
if (editableSchemaMeta && editableSchemaMeta.length > 0) {
|
|
|
|
|
editableSchemaMeta.forEach((updatedField) => {
|
|
|
|
|
const originalFieldIndex = updatedFields.findIndex((f) => f.fieldPath === updatedField.fieldPath);
|
|
|
|
|
if (originalFieldIndex > -1) {
|
|
|
|
|
updatedFields[originalFieldIndex] = {
|
|
|
|
|
...updatedFields[originalFieldIndex],
|
|
|
|
|
description: updatedField.description,
|
|
|
|
|
globalTags: { ...updatedField.globalTags },
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return updatedFields;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function convertEditableSchemaMetadataForUpdate(
|
|
|
|
|
editableSchemaMetadata: EditableSchemaMetadata | null | undefined,
|
|
|
|
|
): EditableSchemaMetadataUpdate {
|
|
|
|
|
return {
|
|
|
|
|
editableSchemaFieldInfo:
|
2025-02-17 22:15:47 +05:30
|
|
|
editableSchemaMetadata?.editableSchemaFieldInfo?.map((editableSchemaFieldInfo) => ({
|
2025-01-29 20:42:01 -05:00
|
|
|
fieldPath: editableSchemaFieldInfo?.fieldPath,
|
|
|
|
|
description: editableSchemaFieldInfo?.description,
|
|
|
|
|
globalTags: { tags: convertTagsForUpdate(editableSchemaFieldInfo?.globalTags?.tags || []) },
|
|
|
|
|
})) || [],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function filterKeyFieldPath(showKeySchema: boolean, field: SchemaField) {
|
|
|
|
|
return field.fieldPath.indexOf(KEY_SCHEMA_PREFIX) > -1 ? showKeySchema : !showKeySchema;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function downgradeV2FieldPath(fieldPath?: string | null) {
|
|
|
|
|
if (!fieldPath) {
|
|
|
|
|
return fieldPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cleanedFieldPath = fieldPath.replace(KEY_SCHEMA_PREFIX, '').replace(VERSION_PREFIX, '');
|
|
|
|
|
|
2025-10-24 21:52:45 +01:00
|
|
|
// Remove all bracket annotations (e.g., [0], [*], [key]) from the field path
|
2025-01-29 20:42:01 -05:00
|
|
|
return cleanedFieldPath
|
|
|
|
|
.split('.')
|
2025-10-24 21:52:45 +01:00
|
|
|
.map((segment) => {
|
|
|
|
|
// Remove segments that are entirely brackets (e.g., "[0]", "[*]")
|
|
|
|
|
if (segment.startsWith('[') && segment.endsWith(']')) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
// Remove bracket suffixes from segments (e.g., "addresses[0]" -> "addresses")
|
|
|
|
|
return segment.replace(/\[[^\]]*\]/g, '');
|
|
|
|
|
})
|
2025-01-29 20:42:01 -05:00
|
|
|
.filter(Boolean)
|
|
|
|
|
.join('.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function pathMatchesNewPath(fieldPathA?: string | null, fieldPathB?: string | null) {
|
|
|
|
|
return fieldPathA === fieldPathB || fieldPathA === downgradeV2FieldPath(fieldPathB);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-29 11:18:47 -07:00
|
|
|
export function pathMatchesInsensitiveToV2(fieldPathA?: string | null, fieldPathB?: string | null) {
|
|
|
|
|
return fieldPathA === fieldPathB || downgradeV2FieldPath(fieldPathA) === downgradeV2FieldPath(fieldPathB);
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-10 16:17:13 -05:00
|
|
|
// should use pathMatchesExact when rendering editable info so the user edits the correct field
|
|
|
|
|
export function pathMatchesExact(fieldPathA?: string | null, fieldPathB?: string | null) {
|
|
|
|
|
return fieldPathA === fieldPathB;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-29 20:42:01 -05:00
|
|
|
// group schema fields by fieldPath and grouping for hierarchy in schema table
|
|
|
|
|
export function groupByFieldPath(
|
|
|
|
|
schemaRows?: Array<SchemaField>,
|
|
|
|
|
options: {
|
|
|
|
|
showKeySchema: boolean;
|
|
|
|
|
} = { showKeySchema: false },
|
|
|
|
|
): Array<ExtendedSchemaFields> {
|
|
|
|
|
const rows = [
|
|
|
|
|
...(schemaRows?.filter(filterKeyFieldPath.bind({}, options.showKeySchema)) || []),
|
|
|
|
|
] as Array<ExtendedSchemaFields>;
|
|
|
|
|
|
|
|
|
|
const outputRows: Array<ExtendedSchemaFields> = [];
|
|
|
|
|
const outputRowByPath = {};
|
|
|
|
|
|
|
|
|
|
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
|
|
|
let parentRow: null | ExtendedSchemaFields = null;
|
|
|
|
|
const row = { children: undefined, ...rows[rowIndex], depth: 0 };
|
|
|
|
|
|
|
|
|
|
for (let j = rowIndex - 1; j >= 0; j--) {
|
|
|
|
|
const rowTokens = row.fieldPath.split('.');
|
|
|
|
|
const isQualifyingUnionField = rowTokens[rowTokens.length - 3] === UNION_TOKEN;
|
|
|
|
|
if (isQualifyingUnionField) {
|
|
|
|
|
// in the case of unions, parent will not be a subset of the child
|
|
|
|
|
rowTokens.splice(rowTokens.length - 2, 1);
|
|
|
|
|
const parentPath = rowTokens.join('.');
|
|
|
|
|
|
|
|
|
|
if (rows[j].fieldPath === parentPath) {
|
|
|
|
|
parentRow = outputRowByPath[rows[j].fieldPath];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// In the case of structs, arrays, etc, parent will be the first token from
|
|
|
|
|
// the left of this field's name(last token of the path) that does not enclosed in [].
|
|
|
|
|
let parentPath: null | string = null;
|
|
|
|
|
for (
|
|
|
|
|
let lastParentTokenIndex = rowTokens.length - 2;
|
|
|
|
|
lastParentTokenIndex >= 0;
|
|
|
|
|
--lastParentTokenIndex
|
|
|
|
|
) {
|
|
|
|
|
const lastParentToken: string = rowTokens[lastParentTokenIndex];
|
|
|
|
|
if (lastParentToken && lastParentToken[0] !== '[') {
|
|
|
|
|
parentPath = rowTokens.slice(0, lastParentTokenIndex + 1).join('.');
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (parentPath && rows[j].fieldPath === parentPath) {
|
|
|
|
|
parentRow = outputRowByPath[rows[j].fieldPath];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if the parent field exists in the ouput, add the current row as a child
|
|
|
|
|
if (parentRow) {
|
|
|
|
|
row.depth = (parentRow.depth || 0) + 1;
|
|
|
|
|
row.parent = parentRow;
|
|
|
|
|
parentRow.children = [...(parentRow.children || []), row];
|
|
|
|
|
} else {
|
|
|
|
|
outputRows.push(row);
|
|
|
|
|
}
|
|
|
|
|
outputRowByPath[row.fieldPath] = row;
|
|
|
|
|
}
|
|
|
|
|
return outputRows;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function diffMarkdown(oldStr: string, newStr: string) {
|
|
|
|
|
const diffArray = diff.diffChars(oldStr || '', newStr || '');
|
|
|
|
|
return diffArray
|
|
|
|
|
.map((diffOne) => {
|
|
|
|
|
if (diffOne.added) {
|
|
|
|
|
return `<ins class="diff">${diffOne.value}</ins>`;
|
|
|
|
|
}
|
|
|
|
|
if (diffOne.removed) {
|
|
|
|
|
return `<del class="diff">${diffOne.value}</del>`;
|
|
|
|
|
}
|
|
|
|
|
return diffOne.value;
|
|
|
|
|
})
|
|
|
|
|
.join('');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function diffJson(oldStr: string, newStr: string) {
|
|
|
|
|
const diffArray = diff.diffJson(oldStr || '', newStr || '');
|
|
|
|
|
return diffArray
|
|
|
|
|
.map((diffOne) => {
|
|
|
|
|
if (diffOne.added) {
|
|
|
|
|
return `+${diffOne.value}`;
|
|
|
|
|
}
|
|
|
|
|
if (diffOne.removed) {
|
|
|
|
|
return `-${diffOne.value}`;
|
|
|
|
|
}
|
|
|
|
|
return diffOne.value;
|
|
|
|
|
})
|
|
|
|
|
.join('');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function formatRawSchema(schemaValue?: string | null): string {
|
|
|
|
|
try {
|
|
|
|
|
if (!schemaValue) {
|
|
|
|
|
return schemaValue || '';
|
|
|
|
|
}
|
|
|
|
|
return JSON.stringify(JSON.parse(schemaValue), null, 2);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return schemaValue || '';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getRawSchema(schema: PlatformSchema | undefined | null, showKeySchema: boolean): string {
|
|
|
|
|
if (!schema) {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (schema.__typename === 'TableSchema') {
|
|
|
|
|
return schema.schema;
|
|
|
|
|
}
|
|
|
|
|
if (schema.__typename === 'KeyValueSchema') {
|
|
|
|
|
return showKeySchema ? schema.keySchema : schema.valueSchema;
|
|
|
|
|
}
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get diff summary between two versions and prepare to visualize description diff changes
|
|
|
|
|
export function getDiffSummary(
|
|
|
|
|
currentVersionRows?: Array<SchemaField>,
|
|
|
|
|
previousVersionRows?: Array<SchemaField>,
|
|
|
|
|
options: { showKeySchema: boolean } = { showKeySchema: false },
|
|
|
|
|
): {
|
|
|
|
|
rows: Array<ExtendedSchemaFields>;
|
|
|
|
|
diffSummary: SchemaDiffSummary;
|
|
|
|
|
} {
|
|
|
|
|
let rows = [
|
|
|
|
|
...(currentVersionRows?.filter(filterKeyFieldPath.bind({}, options.showKeySchema)) || []),
|
|
|
|
|
] as Array<ExtendedSchemaFields>;
|
|
|
|
|
const previousRows = [
|
|
|
|
|
...(previousVersionRows?.filter(filterKeyFieldPath.bind({}, options.showKeySchema)) || []),
|
|
|
|
|
] as Array<ExtendedSchemaFields>;
|
|
|
|
|
|
|
|
|
|
const diffSummary: SchemaDiffSummary = {
|
|
|
|
|
added: 0,
|
|
|
|
|
removed: 0,
|
|
|
|
|
updated: 0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (previousVersionRows && previousVersionRows.length > 0) {
|
|
|
|
|
rows.forEach((field, rowIndex) => {
|
|
|
|
|
const relevantPastFieldIndex = previousRows.findIndex(
|
|
|
|
|
(pf) => pf.type === rows[rowIndex].type && pf.fieldPath === rows[rowIndex].fieldPath,
|
|
|
|
|
);
|
|
|
|
|
if (relevantPastFieldIndex > -1) {
|
|
|
|
|
if (previousRows[relevantPastFieldIndex].description !== rows[rowIndex].description) {
|
|
|
|
|
rows[rowIndex] = {
|
|
|
|
|
...rows[rowIndex],
|
|
|
|
|
previousDescription: previousRows[relevantPastFieldIndex].description,
|
|
|
|
|
};
|
|
|
|
|
diffSummary.updated++; // Increase updated row number in diff summary
|
|
|
|
|
}
|
|
|
|
|
previousRows.splice(relevantPastFieldIndex, 1);
|
|
|
|
|
} else {
|
|
|
|
|
rows[rowIndex] = { ...rows[rowIndex], isNewRow: true };
|
|
|
|
|
diffSummary.added++; // Increase added row number in diff summary
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
rows = [...rows, ...previousRows.map((pf) => ({ ...pf, isDeletedRow: true }))];
|
|
|
|
|
diffSummary.removed = previousRows.length; // removed row number in diff summary
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { rows, diffSummary };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we need to calculate excluding collapsed fields because Antd table expects
|
|
|
|
|
// an indexToScroll to only counting based on visible fields
|
|
|
|
|
export function findIndexOfFieldPathExcludingCollapsedFields(
|
|
|
|
|
fieldPath: string,
|
|
|
|
|
expandedRows: Set<string>,
|
|
|
|
|
rows: Array<ExtendedSchemaFields>,
|
|
|
|
|
sorter: SorterResult<any> | undefined,
|
|
|
|
|
compareFn: ((a: any, b: any) => number) | undefined,
|
|
|
|
|
) {
|
|
|
|
|
let index = 0; // This will keep track of the index across recursive calls
|
|
|
|
|
|
|
|
|
|
function search(shadowedRows) {
|
|
|
|
|
let sortedRows = shadowedRows;
|
|
|
|
|
if (sorter?.order === 'ascend') {
|
|
|
|
|
sortedRows = shadowedRows.toSorted(compareFn);
|
|
|
|
|
} else if (sorter?.order === 'descend') {
|
|
|
|
|
sortedRows = shadowedRows.toSorted(compareFn).toReversed();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-restricted-syntax
|
|
|
|
|
for (const row of sortedRows) {
|
|
|
|
|
// eslint-disable-next) {
|
|
|
|
|
// Check if the current row's ID matches the ID we're looking for
|
|
|
|
|
if (row.fieldPath === fieldPath) {
|
|
|
|
|
return index;
|
|
|
|
|
}
|
|
|
|
|
index++; // Increment index for the current row
|
|
|
|
|
|
|
|
|
|
// Check if current row is expanded and has children
|
|
|
|
|
if (expandedRows.has(row.fieldPath) && row.children && row.children.length) {
|
|
|
|
|
const foundIndex = search(row.children); // Recursively search children
|
|
|
|
|
if (foundIndex !== -1) {
|
|
|
|
|
// If found in children, return the found index
|
|
|
|
|
return foundIndex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Return -1 if the ID was not found in this branch
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Start the recursive search
|
|
|
|
|
return search(rows);
|
|
|
|
|
}
|