mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-27 18:07:57 +00:00
feat(schema) Add search filter to Schema tab (#5845)
This commit is contained in:
parent
5a0154f3fe
commit
3fb4fc35f7
@ -74,6 +74,7 @@
|
||||
"react": "^17.0.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^17.0.0",
|
||||
"react-highlighter": "^0.4.3",
|
||||
"react-icons": "4.3.1",
|
||||
"react-js-cron": "^2.1.0",
|
||||
"react-router": "^5.2.0",
|
||||
|
||||
@ -827,7 +827,7 @@ export const container2 = {
|
||||
__typename: 'Container',
|
||||
} as Container;
|
||||
|
||||
const glossaryTerm1 = {
|
||||
export const glossaryTerm1 = {
|
||||
urn: 'urn:li:glossaryTerm:1',
|
||||
type: EntityType.GlossaryTerm,
|
||||
name: 'Another glossary term',
|
||||
@ -1077,7 +1077,7 @@ export const glossaryNode5 = {
|
||||
__typename: 'GlossaryNode',
|
||||
} as GlossaryNode;
|
||||
|
||||
const sampleTag = {
|
||||
export const sampleTag = {
|
||||
urn: 'urn:li:tag:abc-sample-tag',
|
||||
name: 'abc-sample-tag',
|
||||
description: 'sample tag description',
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import React from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { Button, Popover, Select, Tooltip, Typography } from 'antd';
|
||||
import { Button, Input, Popover, Select, Tooltip, Typography } from 'antd';
|
||||
import { debounce } from 'lodash';
|
||||
import {
|
||||
AuditOutlined,
|
||||
CaretDownOutlined,
|
||||
FileTextOutlined,
|
||||
QuestionCircleOutlined,
|
||||
SearchOutlined,
|
||||
TableOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import styled from 'styled-components';
|
||||
import styled from 'styled-components/macro';
|
||||
import CustomPagination from './CustomPagination';
|
||||
import TabToolbar from '../../../../shared/components/styled/TabToolbar';
|
||||
import { SemanticVersionStruct } from '../../../../../../types.generated';
|
||||
@ -16,11 +18,11 @@ import { toRelativeTimeString } from '../../../../../shared/time/timeUtils';
|
||||
import { ANTD_GRAY, REDESIGN_COLORS } from '../../../../shared/constants';
|
||||
import { navigateToVersionedDatasetUrl } from '../../../../shared/tabs/Dataset/Schema/utils/navigateToVersionedDatasetUrl';
|
||||
import SchemaTimeStamps from './SchemaTimeStamps';
|
||||
import getSchemaFilterFromQueryString from '../../../../shared/tabs/Dataset/Schema/utils/getSchemaFilterFromQueryString';
|
||||
|
||||
const SchemaHeaderContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 16px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@ -60,11 +62,12 @@ const ValueButton = styled(Button)<{ $highlighted: boolean }>`
|
||||
|
||||
const KeyValueButtonGroup = styled.div`
|
||||
margin-right: 10px;
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
// Below styles are for buttons on the right side of the Schema Header
|
||||
const RightButtonsGroup = styled.div`
|
||||
padding-left: 5px;
|
||||
&&& {
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
@ -111,6 +114,14 @@ const StyledCaretDownOutlined = styled(CaretDownOutlined)`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledInput = styled(Input)`
|
||||
border-radius: 70px;
|
||||
max-width: 300px;
|
||||
`;
|
||||
|
||||
const MAX_ROWS_BEFORE_DEBOUNCE = 50;
|
||||
const HALF_SECOND_IN_MS = 500;
|
||||
|
||||
type Props = {
|
||||
maxVersion?: number;
|
||||
fetchVersions?: (version1: number, version2: number) => void;
|
||||
@ -128,6 +139,8 @@ type Props = {
|
||||
versionList: Array<SemanticVersionStruct>;
|
||||
showSchemaAuditView: boolean;
|
||||
setShowSchemaAuditView: any;
|
||||
setFilterText: (text: string) => void;
|
||||
numRows: number;
|
||||
};
|
||||
|
||||
export default function SchemaHeader({
|
||||
@ -147,6 +160,8 @@ export default function SchemaHeader({
|
||||
versionList,
|
||||
showSchemaAuditView,
|
||||
setShowSchemaAuditView,
|
||||
setFilterText,
|
||||
numRows,
|
||||
}: Props) {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
@ -182,6 +197,12 @@ export default function SchemaHeader({
|
||||
};
|
||||
const schemaAuditToggleText = showSchemaAuditView ? 'Close column history' : 'View column history';
|
||||
|
||||
const debouncedSetFilterText = debounce(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => setFilterText(e.target.value),
|
||||
numRows > MAX_ROWS_BEFORE_DEBOUNCE ? HALF_SECOND_IN_MS : 0,
|
||||
);
|
||||
const schemaFilter = getSchemaFilterFromQueryString(location);
|
||||
|
||||
const docLink = 'https://datahubproject.io/docs/dev-guides/timeline/';
|
||||
return (
|
||||
<TabToolbar>
|
||||
@ -219,6 +240,15 @@ export default function SchemaHeader({
|
||||
) : (
|
||||
<ShowVersionButton onClick={() => setEditMode?.(true)}>Back</ShowVersionButton>
|
||||
))}
|
||||
{!showRaw && (
|
||||
<StyledInput
|
||||
defaultValue={schemaFilter}
|
||||
placeholder="Search in schema..."
|
||||
onChange={debouncedSetFilterText}
|
||||
allowClear
|
||||
prefix={<SearchOutlined />}
|
||||
/>
|
||||
)}
|
||||
</LeftButtonsGroup>
|
||||
<RightButtonsGroup>
|
||||
<SchemaTimeStamps lastObserved={lastObserved} lastUpdated={lastUpdated} />
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import Highlight from 'react-highlighter';
|
||||
import translateFieldPath from './translateFieldPath';
|
||||
import { ExtendedSchemaFields } from './types';
|
||||
import TypeLabel from '../../../../shared/tabs/Dataset/Schema/components/TypeLabel';
|
||||
@ -32,6 +33,7 @@ const FieldPathText = styled(Typography.Text)`
|
||||
export default function useSchemaTitleRenderer(
|
||||
schemaMetadata: SchemaMetadata | undefined | null,
|
||||
setSelectedFkFieldPath: (params: { fieldPath: string; constraint?: ForeignKeyConstraint | null } | null) => void,
|
||||
filterText: string,
|
||||
) {
|
||||
const [highlightedConstraint, setHighlightedConstraint] = useState<string | null>(null);
|
||||
|
||||
@ -54,7 +56,9 @@ export default function useSchemaTitleRenderer(
|
||||
return (
|
||||
<>
|
||||
<FieldPathContainer>
|
||||
<FieldPathText>{pathToDisplay}</FieldPathText>
|
||||
<FieldPathText>
|
||||
<Highlight search={filterText}>{pathToDisplay}</Highlight>
|
||||
</FieldPathText>
|
||||
<TypeLabel type={record.type} nativeDataType={record.nativeDataType} />
|
||||
{(schemaMetadata?.primaryKeys?.includes(fieldPath) || record.isPartOfKey) && <PrimaryKeyLabel />}
|
||||
{schemaMetadata?.foreignKeys
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Empty } from 'antd';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useLocation } from 'react-router';
|
||||
import { GetDatasetQuery } from '../../../../../../graphql/dataset.generated';
|
||||
import { useGetSchemaBlameQuery, useGetSchemaVersionListQuery } from '../../../../../../graphql/schemaBlame.generated';
|
||||
import SchemaEditableContext from '../../../../../shared/SchemaEditableContext';
|
||||
@ -14,6 +15,10 @@ import { SchemaFieldBlame, SemanticVersionStruct } from '../../../../../../types
|
||||
import SchemaTable from './SchemaTable';
|
||||
import useGetSemanticVersionFromUrlParams from './utils/useGetSemanticVersionFromUrlParams';
|
||||
import { useGetVersionedDatasetQuery } from '../../../../../../graphql/versionedDataset.generated';
|
||||
import { useEntityRegistry } from '../../../../../useEntityRegistry';
|
||||
import { filterSchemaRows } from './utils/filterSchemaRows';
|
||||
import getSchemaFilterFromQueryString from './utils/getSchemaFilterFromQueryString';
|
||||
import useUpdateSchemaFilterQueryString from './utils/updateSchemaFilterQueryString';
|
||||
|
||||
const NoSchema = styled(Empty)`
|
||||
color: ${ANTD_GRAY[6]};
|
||||
@ -26,6 +31,7 @@ const SchemaTableContainer = styled.div`
|
||||
`;
|
||||
export const SchemaTab = ({ properties }: { properties?: any }) => {
|
||||
const { entityData } = useEntityData();
|
||||
const entityRegistry = useEntityRegistry();
|
||||
const baseEntity = useBaseEntity<GetDatasetQuery>();
|
||||
const maybeEntityData = entityData || {};
|
||||
let schemaMetadata: any = maybeEntityData?.schemaMetadata || undefined;
|
||||
@ -33,6 +39,11 @@ export const SchemaTab = ({ properties }: { properties?: any }) => {
|
||||
const datasetUrn: string = baseEntity?.dataset?.urn || '';
|
||||
const usageStats = baseEntity?.dataset?.usageStats;
|
||||
const [showRaw, setShowRaw] = useState(false);
|
||||
const location = useLocation();
|
||||
const schemaFilter = getSchemaFilterFromQueryString(location);
|
||||
const [filterText, setFilterText] = useState(schemaFilter);
|
||||
useUpdateSchemaFilterQueryString(filterText);
|
||||
|
||||
const hasRawSchema = useMemo(
|
||||
() =>
|
||||
schemaMetadata?.platformSchema?.__typename === 'TableSchema' &&
|
||||
@ -113,9 +124,17 @@ export const SchemaTab = ({ properties }: { properties?: any }) => {
|
||||
setShowKeySchema(true);
|
||||
}
|
||||
}, [hasValueSchema, hasKeySchema, setShowKeySchema]);
|
||||
|
||||
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
|
||||
schemaMetadata?.fields,
|
||||
editableSchemaMetadata,
|
||||
filterText,
|
||||
entityRegistry,
|
||||
);
|
||||
|
||||
const rows = useMemo(() => {
|
||||
return groupByFieldPath(schemaMetadata?.fields, { showKeySchema });
|
||||
}, [schemaMetadata, showKeySchema]);
|
||||
return groupByFieldPath(filteredRows, { showKeySchema });
|
||||
}, [showKeySchema, filteredRows]);
|
||||
|
||||
const lastUpdated = getSchemaBlameData?.getSchemaBlame?.version?.semanticVersionTimestamp;
|
||||
const lastObserved = versionedDatasetData.data?.versionedDataset?.schema?.lastObserved;
|
||||
@ -139,6 +158,8 @@ export const SchemaTab = ({ properties }: { properties?: any }) => {
|
||||
versionList={versionList}
|
||||
showSchemaAuditView={showSchemaAuditView}
|
||||
setShowSchemaAuditView={setShowSchemaAuditView}
|
||||
setFilterText={setFilterText}
|
||||
numRows={rows.length}
|
||||
/>
|
||||
<SchemaTableContainer>
|
||||
{/* eslint-disable-next-line no-nested-ternary */}
|
||||
@ -159,6 +180,8 @@ export const SchemaTab = ({ properties }: { properties?: any }) => {
|
||||
usageStats={usageStats}
|
||||
schemaFieldBlameList={schemaFieldBlameList}
|
||||
showSchemaAuditView={showSchemaAuditView}
|
||||
expandedRowsFromFilter={expandedRowsFromFilter as any}
|
||||
filterText={filterText as any}
|
||||
/>
|
||||
</SchemaEditableContext.Provider>
|
||||
</>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import styled from 'styled-components';
|
||||
import {} from 'antd';
|
||||
@ -45,6 +45,8 @@ export type Props = {
|
||||
usageStats?: UsageQueryResult | null;
|
||||
schemaFieldBlameList?: Array<SchemaFieldBlame> | null;
|
||||
showSchemaAuditView: boolean;
|
||||
expandedRowsFromFilter?: Set<string>;
|
||||
filterText?: string;
|
||||
};
|
||||
export default function SchemaTable({
|
||||
rows,
|
||||
@ -54,6 +56,8 @@ export default function SchemaTable({
|
||||
editMode = true,
|
||||
schemaFieldBlameList,
|
||||
showSchemaAuditView,
|
||||
expandedRowsFromFilter = new Set(),
|
||||
filterText = '',
|
||||
}: Props): JSX.Element {
|
||||
const hasUsageStats = useMemo(() => (usageStats?.aggregations?.fields?.length || 0) > 0, [usageStats]);
|
||||
|
||||
@ -63,15 +67,27 @@ export default function SchemaTable({
|
||||
|
||||
const descriptionRender = useDescriptionRenderer(editableSchemaMetadata);
|
||||
const usageStatsRenderer = useUsageStatsRenderer(usageStats);
|
||||
const tagRenderer = useTagsAndTermsRenderer(editableSchemaMetadata, tagHoveredIndex, setTagHoveredIndex, {
|
||||
showTags: true,
|
||||
showTerms: false,
|
||||
});
|
||||
const termRenderer = useTagsAndTermsRenderer(editableSchemaMetadata, tagHoveredIndex, setTagHoveredIndex, {
|
||||
showTags: false,
|
||||
showTerms: true,
|
||||
});
|
||||
const schemaTitleRenderer = useSchemaTitleRenderer(schemaMetadata, setSelectedFkFieldPath);
|
||||
const tagRenderer = useTagsAndTermsRenderer(
|
||||
editableSchemaMetadata,
|
||||
tagHoveredIndex,
|
||||
setTagHoveredIndex,
|
||||
{
|
||||
showTags: true,
|
||||
showTerms: false,
|
||||
},
|
||||
filterText,
|
||||
);
|
||||
const termRenderer = useTagsAndTermsRenderer(
|
||||
editableSchemaMetadata,
|
||||
tagHoveredIndex,
|
||||
setTagHoveredIndex,
|
||||
{
|
||||
showTags: false,
|
||||
showTerms: true,
|
||||
},
|
||||
filterText,
|
||||
);
|
||||
const schemaTitleRenderer = useSchemaTitleRenderer(schemaMetadata, setSelectedFkFieldPath, filterText);
|
||||
const schemaBlameRenderer = useSchemaBlameRenderer(schemaFieldBlameList);
|
||||
|
||||
const onTagTermCell = (record: SchemaField, rowIndex: number | undefined) => ({
|
||||
@ -153,6 +169,17 @@ export default function SchemaTable({
|
||||
allColumns = [...allColumns, blameColumn];
|
||||
}
|
||||
|
||||
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
setExpandedRows((previousRows) => {
|
||||
const finalRowsSet = new Set();
|
||||
expandedRowsFromFilter.forEach((row) => finalRowsSet.add(row));
|
||||
previousRows.forEach((row) => finalRowsSet.add(row));
|
||||
return finalRowsSet as Set<string>;
|
||||
});
|
||||
}, [expandedRowsFromFilter]);
|
||||
|
||||
return (
|
||||
<FkContext.Provider value={selectedFkFieldPath}>
|
||||
<TableContainer>
|
||||
@ -169,9 +196,20 @@ export default function SchemaTable({
|
||||
},
|
||||
}}
|
||||
expandable={{
|
||||
expandedRowKeys: [...Array.from(expandedRows)],
|
||||
defaultExpandAllRows: false,
|
||||
expandRowByClick: false,
|
||||
expandIcon: ExpandIcon,
|
||||
onExpand: (expanded, record) => {
|
||||
if (expanded) {
|
||||
setExpandedRows((previousRows) => new Set(previousRows.add(record.fieldPath)));
|
||||
} else {
|
||||
setExpandedRows((previousRows) => {
|
||||
previousRows.delete(record.fieldPath);
|
||||
return new Set(previousRows);
|
||||
});
|
||||
}
|
||||
},
|
||||
indentSize: 0,
|
||||
}}
|
||||
pagination={false}
|
||||
|
||||
@ -0,0 +1,122 @@
|
||||
import { glossaryTerm1, sampleTag } from '../../../../../../../Mocks';
|
||||
import { SchemaField } from '../../../../../../../types.generated';
|
||||
import { getTestEntityRegistry } from '../../../../../../../utils/test-utils/TestPageContainer';
|
||||
import { filterSchemaRows } from '../utils/filterSchemaRows';
|
||||
|
||||
describe('filterSchemaRows', () => {
|
||||
const testEntityRegistry = getTestEntityRegistry();
|
||||
const rows = [{ fieldPath: 'customer' }, { fieldPath: 'testing' }, { fieldPath: 'shipment' }] as SchemaField[];
|
||||
|
||||
it('should properly filter schema rows based on field name', () => {
|
||||
const filterText = 'test';
|
||||
const editableSchemaMetadata = { editableSchemaFieldInfo: [] };
|
||||
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
|
||||
rows,
|
||||
editableSchemaMetadata,
|
||||
filterText,
|
||||
testEntityRegistry,
|
||||
);
|
||||
|
||||
expect(filteredRows).toMatchObject([{ fieldPath: 'testing' }]);
|
||||
expect(expandedRowsFromFilter).toMatchObject(new Set());
|
||||
});
|
||||
|
||||
it('should properly filter schema rows based on field name regardless of capitalization', () => {
|
||||
const editableSchemaMetadata = { editableSchemaFieldInfo: [] };
|
||||
const filterText = 'TeSt';
|
||||
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
|
||||
rows,
|
||||
editableSchemaMetadata,
|
||||
filterText,
|
||||
testEntityRegistry,
|
||||
);
|
||||
|
||||
expect(filteredRows).toMatchObject([{ fieldPath: 'testing' }]);
|
||||
expect(expandedRowsFromFilter).toMatchObject(new Set());
|
||||
});
|
||||
|
||||
it('should properly filter schema rows based on tags', () => {
|
||||
const editableSchemaMetadata = {
|
||||
editableSchemaFieldInfo: [
|
||||
{ fieldPath: 'customer', globalTags: { tags: [{ tag: sampleTag }] }, glossaryTerms: null },
|
||||
],
|
||||
};
|
||||
const filterText = sampleTag.properties.name;
|
||||
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
|
||||
rows,
|
||||
editableSchemaMetadata,
|
||||
filterText,
|
||||
testEntityRegistry,
|
||||
);
|
||||
|
||||
expect(filteredRows).toMatchObject([{ fieldPath: 'customer' }]);
|
||||
expect(expandedRowsFromFilter).toMatchObject(new Set());
|
||||
});
|
||||
|
||||
it('should properly filter schema rows based on glossary terms', () => {
|
||||
const editableSchemaMetadata = {
|
||||
editableSchemaFieldInfo: [
|
||||
{ fieldPath: 'shipment', globalTags: null, glossaryTerms: { terms: [{ term: glossaryTerm1 }] } },
|
||||
],
|
||||
};
|
||||
const filterText = glossaryTerm1.properties?.name as string;
|
||||
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
|
||||
rows,
|
||||
editableSchemaMetadata,
|
||||
filterText,
|
||||
testEntityRegistry,
|
||||
);
|
||||
|
||||
expect(filteredRows).toMatchObject([{ fieldPath: 'shipment' }]);
|
||||
expect(expandedRowsFromFilter).toMatchObject(new Set());
|
||||
});
|
||||
|
||||
it('should properly filter and find children fields', () => {
|
||||
const rowsWithChildren = [
|
||||
{ fieldPath: 'customer' },
|
||||
{ fieldPath: 'testing' },
|
||||
{ fieldPath: 'customer.child1' },
|
||||
{ fieldPath: 'customer.child2' },
|
||||
] as SchemaField[];
|
||||
const editableSchemaMetadata = { editableSchemaFieldInfo: [] };
|
||||
const filterText = 'child';
|
||||
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
|
||||
rowsWithChildren,
|
||||
editableSchemaMetadata,
|
||||
filterText,
|
||||
testEntityRegistry,
|
||||
);
|
||||
|
||||
expect(filteredRows).toMatchObject([
|
||||
{ fieldPath: 'customer' },
|
||||
{ fieldPath: 'customer.child1' },
|
||||
{ fieldPath: 'customer.child2' },
|
||||
]);
|
||||
expect(expandedRowsFromFilter).toMatchObject(new Set(['customer']));
|
||||
});
|
||||
|
||||
it('should properly filter and find children fields multiple levels down', () => {
|
||||
const rowsWithChildren = [
|
||||
{ fieldPath: 'customer' },
|
||||
{ fieldPath: 'testing' },
|
||||
{ fieldPath: 'customer.child1' },
|
||||
{ fieldPath: 'customer.child1.findMe' },
|
||||
{ fieldPath: 'customer.child2' },
|
||||
] as SchemaField[];
|
||||
const editableSchemaMetadata = { editableSchemaFieldInfo: [] };
|
||||
const filterText = 'find';
|
||||
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
|
||||
rowsWithChildren,
|
||||
editableSchemaMetadata,
|
||||
filterText,
|
||||
testEntityRegistry,
|
||||
);
|
||||
|
||||
expect(filteredRows).toMatchObject([
|
||||
{ fieldPath: 'customer' },
|
||||
{ fieldPath: 'customer.child1' },
|
||||
{ fieldPath: 'customer.child1.findMe' },
|
||||
]);
|
||||
expect(expandedRowsFromFilter).toMatchObject(new Set(['customer', 'customer.child1']));
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,74 @@
|
||||
import { EntityType, SchemaField } from '../../../../../../../types.generated';
|
||||
import EntityRegistry from '../../../../../EntityRegistry';
|
||||
|
||||
// returns list of fieldPaths for fields that have Terms or Tags matching the filterText
|
||||
function getFilteredFieldPathsByMetadata(editableSchemaMetadata: any, entityRegistry, filterText) {
|
||||
return (
|
||||
editableSchemaMetadata?.editableSchemaFieldInfo
|
||||
.filter((fieldInfo) => {
|
||||
return (
|
||||
fieldInfo.globalTags?.tags.find((tagAssociation) =>
|
||||
entityRegistry
|
||||
.getDisplayName(EntityType.Tag, tagAssociation.tag)
|
||||
.toLocaleLowerCase()
|
||||
.includes(filterText),
|
||||
) ||
|
||||
fieldInfo.glossaryTerms?.terms.find((termAssociation) =>
|
||||
entityRegistry
|
||||
.getDisplayName(EntityType.GlossaryTerm, termAssociation.term)
|
||||
.toLocaleLowerCase()
|
||||
.includes(filterText),
|
||||
)
|
||||
);
|
||||
})
|
||||
.map((fieldInfo) => fieldInfo.fieldPath) || []
|
||||
);
|
||||
}
|
||||
|
||||
function shouldInclude(
|
||||
fieldName: string,
|
||||
fullFieldPath: string,
|
||||
filterText: string,
|
||||
filteredFieldPathsByMetadata: any,
|
||||
) {
|
||||
return fieldName.toLocaleLowerCase().includes(filterText) || filteredFieldPathsByMetadata.includes(fullFieldPath);
|
||||
}
|
||||
|
||||
export function filterSchemaRows(
|
||||
rows: SchemaField[],
|
||||
editableSchemaMetadata: any,
|
||||
filterText: string,
|
||||
entityRegistry: EntityRegistry,
|
||||
) {
|
||||
if (!rows) return { filteredRows: [], expandedRowsFromFilter: new Set() };
|
||||
if (!filterText) return { filteredRows: rows, expandedRowsFromFilter: new Set() };
|
||||
const formattedFilterText = filterText.toLocaleLowerCase();
|
||||
|
||||
const filteredFieldPathsByMetadata = getFilteredFieldPathsByMetadata(
|
||||
editableSchemaMetadata,
|
||||
entityRegistry,
|
||||
formattedFilterText,
|
||||
);
|
||||
const finalFieldPaths = new Set();
|
||||
const expandedRowsFromFilter = new Set();
|
||||
|
||||
rows.forEach((row) => {
|
||||
if (shouldInclude(row.fieldPath, row.fieldPath, formattedFilterText, filteredFieldPathsByMetadata)) {
|
||||
finalFieldPaths.add(row.fieldPath);
|
||||
}
|
||||
const splitFieldPath = row.fieldPath.split('.');
|
||||
const fieldName = splitFieldPath.slice(-1)[0];
|
||||
if (shouldInclude(fieldName, row.fieldPath, formattedFilterText, filteredFieldPathsByMetadata)) {
|
||||
// if we match specifically on this field (not just its parent), add and expand all parents
|
||||
splitFieldPath.reduce((previous, current) => {
|
||||
finalFieldPaths.add(previous);
|
||||
expandedRowsFromFilter.add(previous);
|
||||
return `${previous}.${current}`;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const filteredRows = rows.filter((row) => finalFieldPaths.has(row.fieldPath));
|
||||
|
||||
return { filteredRows, expandedRowsFromFilter };
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
import * as QueryString from 'query-string';
|
||||
|
||||
export default function getSchemaFilterFromQueryString(location: any) {
|
||||
const params = QueryString.parse(location.search, { arrayFormat: 'comma' });
|
||||
return decodeURIComponent(params.schemaFilter ? (params.schemaFilter as string) : '');
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import { useHistory, useLocation } from 'react-router';
|
||||
import * as QueryString from 'query-string';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export default function useUpdateSchemaFilterQueryString(filterText: string) {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const parsedParams = QueryString.parse(location.search, { arrayFormat: 'comma' });
|
||||
const newParams = {
|
||||
...parsedParams,
|
||||
schemaFilter: filterText,
|
||||
};
|
||||
const stringifiedParams = QueryString.stringify(newParams, { arrayFormat: 'comma' });
|
||||
|
||||
useEffect(() => {
|
||||
history.push({
|
||||
pathname: location.pathname,
|
||||
search: stringifiedParams,
|
||||
});
|
||||
}, [filterText, history, location.pathname, stringifiedParams]);
|
||||
}
|
||||
@ -9,6 +9,7 @@ export default function useTagsAndTermsRenderer(
|
||||
tagHoveredIndex: string | undefined,
|
||||
setTagHoveredIndex: (index: string | undefined) => void,
|
||||
options: { showTags: boolean; showTerms: boolean },
|
||||
filterText: string,
|
||||
) {
|
||||
const urn = useMutationUrn();
|
||||
const refetch = useRefetch();
|
||||
@ -33,6 +34,7 @@ export default function useTagsAndTermsRenderer(
|
||||
entityUrn={urn}
|
||||
entityType={EntityType.Dataset}
|
||||
entitySubresource={record.fieldPath}
|
||||
highlightText={filterText}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -3,6 +3,7 @@ import React, { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { BookOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import Highlight from 'react-highlighter';
|
||||
|
||||
import { useEntityRegistry } from '../../useEntityRegistry';
|
||||
import {
|
||||
@ -38,6 +39,7 @@ type Props = {
|
||||
entityUrn?: string;
|
||||
entityType?: EntityType;
|
||||
entitySubresource?: string;
|
||||
highlightText?: string;
|
||||
refetch?: () => Promise<any>;
|
||||
};
|
||||
|
||||
@ -62,6 +64,8 @@ const TagText = styled.span`
|
||||
margin: 0 7px 0 0;
|
||||
`;
|
||||
|
||||
const highlightMatchStyle = { background: '#ffe58f', padding: '0' };
|
||||
|
||||
export default function TagTermGroup({
|
||||
uneditableTags,
|
||||
editableTags,
|
||||
@ -78,6 +82,7 @@ export default function TagTermGroup({
|
||||
entityUrn,
|
||||
entityType,
|
||||
entitySubresource,
|
||||
highlightText,
|
||||
refetch,
|
||||
}: Props) {
|
||||
const entityRegistry = useEntityRegistry();
|
||||
@ -188,9 +193,11 @@ export default function TagTermGroup({
|
||||
if (maxShow && renderedTags === maxShow + 1)
|
||||
return (
|
||||
<TagText>
|
||||
{uneditableGlossaryTerms?.terms
|
||||
? `+${uneditableGlossaryTerms?.terms?.length - maxShow}`
|
||||
: null}
|
||||
<Highlight matchStyle={highlightMatchStyle} search={highlightText}>
|
||||
{uneditableGlossaryTerms?.terms
|
||||
? `+${uneditableGlossaryTerms?.terms?.length - maxShow}`
|
||||
: null}
|
||||
</Highlight>
|
||||
</TagText>
|
||||
);
|
||||
if (maxShow && renderedTags > maxShow) return null;
|
||||
@ -224,7 +231,9 @@ export default function TagTermGroup({
|
||||
}}
|
||||
>
|
||||
<BookOutlined style={{ marginRight: '3%' }} />
|
||||
{entityRegistry.getDisplayName(EntityType.GlossaryTerm, term.term)}
|
||||
<Highlight matchStyle={highlightMatchStyle} search={highlightText}>
|
||||
{entityRegistry.getDisplayName(EntityType.GlossaryTerm, term.term)}
|
||||
</Highlight>
|
||||
</Tag>
|
||||
</TermLink>
|
||||
</HoverEntityTooltip>
|
||||
@ -238,9 +247,10 @@ export default function TagTermGroup({
|
||||
);
|
||||
if (maxShow && renderedTags > maxShow) return null;
|
||||
|
||||
const displayName = entityRegistry.getDisplayName(EntityType.Tag, tag.tag);
|
||||
return (
|
||||
<HoverEntityTooltip entity={tag?.tag}>
|
||||
<TagLink key={tag?.tag?.urn}>
|
||||
<TagLink key={tag?.tag?.urn} data-testid={`tag-${displayName}`}>
|
||||
<StyledTag
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => showTagProfileDrawer(tag?.tag?.urn)}
|
||||
@ -248,7 +258,9 @@ export default function TagTermGroup({
|
||||
$color={tag?.tag?.properties?.colorHex}
|
||||
closable={false}
|
||||
>
|
||||
{entityRegistry.getDisplayName(EntityType.Tag, tag.tag)}
|
||||
<Highlight matchStyle={highlightMatchStyle} search={highlightText}>
|
||||
{displayName}
|
||||
</Highlight>
|
||||
</StyledTag>
|
||||
</TagLink>
|
||||
</HoverEntityTooltip>
|
||||
@ -258,9 +270,11 @@ export default function TagTermGroup({
|
||||
{editableTags?.tags?.map((tag) => {
|
||||
renderedTags += 1;
|
||||
if (maxShow && renderedTags > maxShow) return null;
|
||||
|
||||
const displayName = entityRegistry.getDisplayName(EntityType.Tag, tag.tag);
|
||||
return (
|
||||
<HoverEntityTooltip entity={tag?.tag}>
|
||||
<TagLink>
|
||||
<TagLink data-testid={`tag-${displayName}`}>
|
||||
<StyledTag
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => showTagProfileDrawer(tag?.tag?.urn)}
|
||||
@ -272,7 +286,9 @@ export default function TagTermGroup({
|
||||
removeTag(tag);
|
||||
}}
|
||||
>
|
||||
{tag?.tag?.name}
|
||||
<Highlight matchStyle={highlightMatchStyle} search={highlightText}>
|
||||
{displayName}
|
||||
</Highlight>
|
||||
</StyledTag>
|
||||
</TagLink>
|
||||
</HoverEntityTooltip>
|
||||
|
||||
@ -4887,6 +4887,11 @@ bindings@^1.5.0:
|
||||
dependencies:
|
||||
file-uri-to-path "1.0.0"
|
||||
|
||||
blacklist@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/blacklist/-/blacklist-1.1.4.tgz#b2dd09d6177625b2caa69835a37b28995fa9a2f2"
|
||||
integrity sha512-DWdfwimA1WQxVC69Vs1Fy525NbYwisMSCdYQmW9zyzOByz9OB/tQwrKZ3T3pbTkuFjnkJFlJuyiDjPiXL5kzew==
|
||||
|
||||
blob-util@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb"
|
||||
@ -6049,6 +6054,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
|
||||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
create-react-class@^15.6.2:
|
||||
version "15.7.0"
|
||||
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.7.0.tgz#7499d7ca2e69bb51d13faf59bd04f0c65a1d6c1e"
|
||||
integrity sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==
|
||||
dependencies:
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
create-require@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
|
||||
@ -13963,6 +13976,16 @@ react-error-overlay@^6.0.9:
|
||||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
|
||||
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
|
||||
|
||||
react-highlighter@^0.4.3:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/react-highlighter/-/react-highlighter-0.4.3.tgz#e32c84d053259c30ca72c615aa759036d0d23048"
|
||||
integrity sha512-dwItRaGRHBceuzZd5NXeroapdmZ2JCAWZ3AdwdthRlSkdtPCY18DWrd6mPmiMCfSB6lgVwwCPQl4unZzG5sXXw==
|
||||
dependencies:
|
||||
blacklist "^1.1.4"
|
||||
create-react-class "^15.6.2"
|
||||
escape-string-regexp "^1.0.5"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react-icons@4.3.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca"
|
||||
|
||||
@ -50,7 +50,7 @@ describe("mutations", () => {
|
||||
// verify dataset shows up in search now
|
||||
cy.contains("of 1 result").click({ force: true });
|
||||
cy.contains("cypress_logging_events").click({ force: true });
|
||||
cy.contains("CypressTestAddTag").within(() =>
|
||||
cy.get('[data-testid="tag-CypressTestAddTag"]').within(() =>
|
||||
cy.get("span[aria-label=close]").click()
|
||||
);
|
||||
cy.contains("Yes").click();
|
||||
@ -138,7 +138,7 @@ describe("mutations", () => {
|
||||
// verify dataset shows up in search now
|
||||
cy.contains("of 1 result").click();
|
||||
cy.contains("cypress_logging_events").click();
|
||||
cy.contains("CypressTestAddTag2").within(() =>
|
||||
cy.get('[data-testid="tag-CypressTestAddTag2"]').within(() =>
|
||||
cy
|
||||
.get("span[aria-label=close]")
|
||||
.trigger("mouseover", { force: true })
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user