mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-26 01:18:20 +00:00
fix(ui): Backfill proposals changes (#13350)
This commit is contained in:
parent
afa9209f5d
commit
3154289dd8
@ -1,3 +1,4 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { Dropdown, Text } from '@components';
|
||||
import { isEqual } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
@ -23,6 +24,9 @@ import SelectLabelRenderer from '@components/components/Select/private/SelectLab
|
||||
import useSelectDropdown from '@components/components/Select/private/hooks/useSelectDropdown';
|
||||
import { SelectOption, SelectProps } from '@components/components/Select/types';
|
||||
|
||||
import NoResultsFoundPlaceholder from '@app/searchV2/searchBarV2/components/NoResultsFoundPlaceholder';
|
||||
import { LoadingWrapper } from '@src/app/entityV2/shared/tabs/Incident/AcrylComponents/styledComponents';
|
||||
|
||||
export const selectDefaults: SelectProps = {
|
||||
options: [],
|
||||
label: '',
|
||||
@ -75,6 +79,7 @@ export const SimpleSelect = ({
|
||||
position,
|
||||
applyHoverWidth,
|
||||
ignoreMaxHeight = selectDefaults.ignoreMaxHeight,
|
||||
isLoading = false,
|
||||
...props
|
||||
}: SelectProps) => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
@ -183,6 +188,13 @@ export const SimpleSelect = ({
|
||||
size={size}
|
||||
/>
|
||||
)}
|
||||
{isLoading ? (
|
||||
<LoadingWrapper>
|
||||
<LoadingOutlined />
|
||||
</LoadingWrapper>
|
||||
) : (
|
||||
!filteredOptions.length && <NoResultsFoundPlaceholder />
|
||||
)}
|
||||
<OptionList style={optionListStyle} data-testid={optionListTestId}>
|
||||
{showSelectAll && isMultiSelect && (
|
||||
<DropdownSelectAllOption
|
||||
|
||||
@ -55,6 +55,7 @@ export interface SelectProps<OptionType extends SelectOption = SelectOption> {
|
||||
position?: OptionPosition;
|
||||
applyHoverWidth?: boolean;
|
||||
ignoreMaxHeight?: boolean;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export interface SelectStyleProps {
|
||||
|
||||
@ -15,13 +15,16 @@ const CardSkeleton = styled(Skeleton.Input)`
|
||||
}
|
||||
`;
|
||||
|
||||
export default function SearchFiltersLoadingSection() {
|
||||
interface Props {
|
||||
noOfLoadingSkeletons?: number;
|
||||
}
|
||||
|
||||
export default function SearchFiltersLoadingSection({ noOfLoadingSkeletons = 4 }: Props) {
|
||||
return (
|
||||
<Container>
|
||||
<CardSkeleton active size="default" />
|
||||
<CardSkeleton active size="default" />
|
||||
<CardSkeleton active size="default" />
|
||||
<CardSkeleton active size="default" />
|
||||
{Array.from({ length: noOfLoadingSkeletons }).map(() => (
|
||||
<CardSkeleton active size="default" />
|
||||
))}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
PlatformIcon,
|
||||
canCreateViewFromFilters,
|
||||
combineAggregations,
|
||||
deduplicateAggregations,
|
||||
filterEmptyAggregations,
|
||||
filterOptionsWithSearch,
|
||||
getFilterDisplayName,
|
||||
@ -26,7 +27,7 @@ import { dataPlatform, dataPlatformInstance, dataset1, glossaryTerm1, user1 } fr
|
||||
import { DATE_TYPE_URN } from '@src/app/shared/constants';
|
||||
import { getTestEntityRegistry } from '@utils/test-utils/TestPageContainer';
|
||||
|
||||
import { EntityType } from '@types';
|
||||
import { AggregationMetadata, EntityType } from '@types';
|
||||
|
||||
describe('filter utils - getNewFilters', () => {
|
||||
it('should get the correct list of filters when adding filters where the filter field did not already exist', () => {
|
||||
@ -358,6 +359,81 @@ describe('filter utils - filterEmptyAggregations', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('deduplicateAggregations() deduplicateAggregations method', () => {
|
||||
// Happy Path Tests
|
||||
describe('Happy Paths', () => {
|
||||
it('should return an empty array when both baseAggs and secondaryAggs are empty', () => {
|
||||
const baseAggs: AggregationMetadata[] = [];
|
||||
const secondaryAggs: AggregationMetadata[] = [];
|
||||
const result = deduplicateAggregations(baseAggs, secondaryAggs);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return secondaryAggs when baseAggs is empty', () => {
|
||||
const baseAggs: AggregationMetadata[] = [];
|
||||
const secondaryAggs: AggregationMetadata[] = [
|
||||
{ count: 0, value: 'value1' },
|
||||
{ count: 0, value: 'value2' },
|
||||
];
|
||||
const result = deduplicateAggregations(baseAggs, secondaryAggs);
|
||||
expect(result).toEqual(secondaryAggs);
|
||||
});
|
||||
|
||||
it('should return an empty array when all secondaryAggs are in baseAggs', () => {
|
||||
const baseAggs: AggregationMetadata[] = [
|
||||
{ count: 0, value: 'value1' },
|
||||
{ count: 0, value: 'value2' },
|
||||
];
|
||||
const secondaryAggs: AggregationMetadata[] = [
|
||||
{ count: 1, value: 'value1' },
|
||||
{ count: 2, value: 'value2' },
|
||||
];
|
||||
const result = deduplicateAggregations(baseAggs, secondaryAggs);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return only the unique secondaryAggs not present in baseAggs', () => {
|
||||
const baseAggs: AggregationMetadata[] = [{ count: 0, value: 'value1' }];
|
||||
const secondaryAggs: AggregationMetadata[] = [
|
||||
{ count: 0, value: 'value1' },
|
||||
{ count: 2, value: 'value2' },
|
||||
];
|
||||
const result = deduplicateAggregations(baseAggs, secondaryAggs);
|
||||
expect(result).toEqual([{ count: 2, value: 'value2' }]);
|
||||
});
|
||||
});
|
||||
|
||||
// Edge Case Tests
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle case sensitivity correctly', () => {
|
||||
const baseAggs: AggregationMetadata[] = [{ count: 0, value: 'Value1' }];
|
||||
const secondaryAggs: AggregationMetadata[] = [
|
||||
{ count: 0, value: 'value1' },
|
||||
{ count: 0, value: 'Value2' },
|
||||
];
|
||||
const result = deduplicateAggregations(baseAggs, secondaryAggs);
|
||||
expect(result).toEqual([
|
||||
{ count: 0, value: 'value1' },
|
||||
{ count: 0, value: 'Value2' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle large arrays efficiently', () => {
|
||||
const baseAggs: AggregationMetadata[] = Array.from({ length: 1000 }, (_, i) => ({
|
||||
count: 0,
|
||||
value: `value${i}`,
|
||||
}));
|
||||
const secondaryAggs: AggregationMetadata[] = Array.from({ length: 2000 }, (_, i) => ({
|
||||
count: 0,
|
||||
value: `value${i}`,
|
||||
}));
|
||||
const result = deduplicateAggregations(baseAggs, secondaryAggs);
|
||||
expect(result.length).toBe(1000);
|
||||
expect(result[0]).toEqual({ count: 0, value: 'value1000' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('filter utils - getFilterOptions', () => {
|
||||
const originalAggs = [
|
||||
{ value: 'aditya', count: 10 },
|
||||
|
||||
@ -1,16 +1,22 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
import { filterEmptyAggregations, getNewFilters, getNumActiveFiltersForFilter } from '@app/searchV2/filters/utils';
|
||||
import {
|
||||
deduplicateAggregations,
|
||||
filterEmptyAggregations,
|
||||
getNewFilters,
|
||||
getNumActiveFiltersForFilter,
|
||||
} from '@app/searchV2/filters/utils';
|
||||
import useGetSearchQueryInputs from '@app/searchV2/useGetSearchQueryInputs';
|
||||
import { ENTITY_FILTER_NAME } from '@app/searchV2/utils/constants';
|
||||
import { useAggregateAcrossEntitiesLazyQuery } from '@src/graphql/search.generated';
|
||||
|
||||
import { FacetFilterInput, FacetMetadata } from '@types';
|
||||
import { EntityType, FacetFilterInput, FacetMetadata } from '@types';
|
||||
|
||||
interface Props {
|
||||
filter: FacetMetadata;
|
||||
activeFilters: FacetFilterInput[];
|
||||
onChangeFilters: (newFilters: FacetFilterInput[]) => void;
|
||||
aggregationsEntityTypes?: Array<EntityType>;
|
||||
shouldUseAggregationsFromFilter?: boolean;
|
||||
}
|
||||
|
||||
@ -18,6 +24,7 @@ export default function useSearchFilterDropdown({
|
||||
filter,
|
||||
activeFilters,
|
||||
onChangeFilters,
|
||||
aggregationsEntityTypes,
|
||||
shouldUseAggregationsFromFilter,
|
||||
}: Props) {
|
||||
const numActiveFilters = getNumActiveFiltersForFilter(activeFilters, filter);
|
||||
@ -36,7 +43,7 @@ export default function useSearchFilterDropdown({
|
||||
aggregateAcrossEntities({
|
||||
variables: {
|
||||
input: {
|
||||
types: filter.field === ENTITY_FILTER_NAME ? null : entityFilters,
|
||||
types: aggregationsEntityTypes || (filter.field === ENTITY_FILTER_NAME ? null : entityFilters),
|
||||
query,
|
||||
orFilters,
|
||||
viewUrn,
|
||||
@ -45,11 +52,27 @@ export default function useSearchFilterDropdown({
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [aggregateAcrossEntities, entityFilters, filter.field, orFilters, query, viewUrn, shouldFetchAggregations]);
|
||||
}, [
|
||||
aggregateAcrossEntities,
|
||||
entityFilters,
|
||||
filter.field,
|
||||
orFilters,
|
||||
query,
|
||||
viewUrn,
|
||||
shouldFetchAggregations,
|
||||
aggregationsEntityTypes,
|
||||
]);
|
||||
|
||||
const fetchedAggregations =
|
||||
data?.aggregateAcrossEntities?.facets?.find((f) => f.field === filter.field)?.aggregations || [];
|
||||
const searchAggregations = filter.aggregations;
|
||||
const activeAggregations = searchAggregations.filter((agg) =>
|
||||
activeFilters.find((f) => f.values?.includes(agg.value) || f.value === agg.value),
|
||||
);
|
||||
|
||||
const aggregations = shouldFetchAggregations
|
||||
? data?.aggregateAcrossEntities?.facets?.[0]?.aggregations
|
||||
: filter.aggregations;
|
||||
? [...fetchedAggregations, ...deduplicateAggregations(fetchedAggregations, activeAggregations)]
|
||||
: searchAggregations;
|
||||
|
||||
const finalAggregations = filterEmptyAggregations(aggregations || [], activeFilters);
|
||||
|
||||
|
||||
@ -293,6 +293,12 @@ export function filterEmptyAggregations(aggregations: AggregationMetadata[], act
|
||||
});
|
||||
}
|
||||
|
||||
// Filters out values from the secondary aggregations that are present in the base aggregations.
|
||||
export const deduplicateAggregations = (baseAggs: AggregationMetadata[], secondaryAggs: AggregationMetadata[]) => {
|
||||
const baseValues = baseAggs.map((agg) => agg.value);
|
||||
return secondaryAggs.filter((agg) => !baseValues.includes(agg.value));
|
||||
};
|
||||
|
||||
export function sortFacets(facetA: FacetMetadata, facetB: FacetMetadata, sortedFacetFields: string[]) {
|
||||
if (sortedFacetFields.indexOf(facetA.field) === -1) return 1;
|
||||
if (sortedFacetFields.indexOf(facetB.field) === -1) return -1;
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
} from '@app/searchV2/filters/value/utils';
|
||||
import { ENTITY_SUB_TYPE_FILTER_NAME, FILTER_DELIMITER } from '@app/searchV2/utils/constants';
|
||||
import { useEntityRegistry } from '@app/useEntityRegistry';
|
||||
import { EntityType } from '@src/types.generated';
|
||||
|
||||
interface Props {
|
||||
field: FilterField;
|
||||
@ -23,6 +24,7 @@ interface Props {
|
||||
includeSubTypes?: boolean;
|
||||
includeCount?: boolean;
|
||||
className?: string;
|
||||
aggregationsEntityTypes?: Array<EntityType>;
|
||||
}
|
||||
|
||||
export default function EntityTypeMenu({
|
||||
@ -35,6 +37,7 @@ export default function EntityTypeMenu({
|
||||
includeSubTypes = true,
|
||||
includeCount = false,
|
||||
className,
|
||||
aggregationsEntityTypes,
|
||||
}: Props) {
|
||||
const entityRegistry = useEntityRegistry();
|
||||
const { displayName } = field;
|
||||
@ -43,7 +46,12 @@ export default function EntityTypeMenu({
|
||||
const [searchQuery, setSearchQuery] = useState<string | undefined>(undefined);
|
||||
|
||||
// Here we optionally load the aggregation options, which are the options that are displayed by default.
|
||||
const { options: aggOptions, loading: aggLoading } = useLoadAggregationOptions(field, true, includeCount);
|
||||
const { options: aggOptions, loading: aggLoading } = useLoadAggregationOptions({
|
||||
field,
|
||||
visible: true,
|
||||
includeCounts: includeCount,
|
||||
aggregationsEntityTypes,
|
||||
});
|
||||
|
||||
const allOptions = [...defaultOptions, ...deduplicateOptions(defaultOptions, aggOptions)];
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
useLoadAggregationOptions,
|
||||
} from '@app/searchV2/filters/value/utils';
|
||||
import { useEntityRegistry } from '@app/useEntityRegistry';
|
||||
import { EntityType } from '@src/types.generated';
|
||||
|
||||
interface Props {
|
||||
field: FilterField;
|
||||
@ -21,6 +22,7 @@ interface Props {
|
||||
type?: 'card' | 'default';
|
||||
includeCount?: boolean;
|
||||
className?: string;
|
||||
aggregationsEntityTypes?: Array<EntityType>;
|
||||
}
|
||||
|
||||
export default function EnumValueMenu({
|
||||
@ -32,6 +34,7 @@ export default function EnumValueMenu({
|
||||
onChangeValues,
|
||||
onApply,
|
||||
className,
|
||||
aggregationsEntityTypes,
|
||||
}: Props) {
|
||||
const entityRegistry = useEntityRegistry();
|
||||
const displayName = useFilterDisplayName(field);
|
||||
@ -40,7 +43,12 @@ export default function EnumValueMenu({
|
||||
const [searchQuery, setSearchQuery] = useState<string | undefined>(undefined);
|
||||
|
||||
// Here we optionally load the aggregation options, which are the options that are displayed by default.
|
||||
const { options: aggOptions, loading: aggLoading } = useLoadAggregationOptions(field, true, includeCount);
|
||||
const { options: aggOptions, loading: aggLoading } = useLoadAggregationOptions({
|
||||
field,
|
||||
visible: true,
|
||||
includeCounts: includeCount,
|
||||
aggregationsEntityTypes,
|
||||
});
|
||||
|
||||
const allOptions = [...defaultOptions, ...deduplicateOptions(defaultOptions, aggOptions)];
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import EntityValueMenu from '@app/searchV2/filters/value/EntityValueMenu';
|
||||
import EnumValueMenu from '@app/searchV2/filters/value/EnumValueMenu';
|
||||
import TextValueMenu from '@app/searchV2/filters/value/TextValueMenu';
|
||||
import TimeBucketMenu from '@app/searchV2/filters/value/TimeBucketMenu';
|
||||
import { FacetFilterInput } from '@src/types.generated';
|
||||
import { EntityType, FacetFilterInput } from '@src/types.generated';
|
||||
|
||||
interface Props {
|
||||
field: FilterField;
|
||||
@ -22,6 +22,7 @@ interface Props {
|
||||
includeCount?: boolean;
|
||||
className?: string;
|
||||
manuallyUpdateFilters?: (newValues: FacetFilterInput[]) => void;
|
||||
aggregationsEntityTypes?: Array<EntityType>;
|
||||
}
|
||||
|
||||
export default function ValueMenu({
|
||||
@ -34,6 +35,7 @@ export default function ValueMenu({
|
||||
includeCount,
|
||||
className,
|
||||
manuallyUpdateFilters,
|
||||
aggregationsEntityTypes,
|
||||
}: Props) {
|
||||
const [stagedSelectedValues, setStagedSelectedValues] = useState<FilterValue[]>(values || []);
|
||||
const visibilityRef = useRef<boolean>(visible);
|
||||
@ -122,6 +124,7 @@ export default function ValueMenu({
|
||||
className={className}
|
||||
onChangeValues={setStagedSelectedValues}
|
||||
onApply={() => onChangeValues(stagedSelectedValues)}
|
||||
aggregationsEntityTypes={aggregationsEntityTypes}
|
||||
/>
|
||||
);
|
||||
case FieldType.ENUM:
|
||||
@ -135,6 +138,7 @@ export default function ValueMenu({
|
||||
className={className}
|
||||
onChangeValues={setStagedSelectedValues}
|
||||
onApply={() => onChangeValues(stagedSelectedValues)}
|
||||
aggregationsEntityTypes={aggregationsEntityTypes}
|
||||
/>
|
||||
);
|
||||
case FieldType.BUCKETED_TIMESTAMP:
|
||||
|
||||
@ -4,7 +4,7 @@ import React, { useState } from 'react';
|
||||
|
||||
import { FilterField, FilterValue, FilterValueOption } from '@app/searchV2/filters/types';
|
||||
import ValueMenu from '@app/searchV2/filters/value/ValueMenu';
|
||||
import { FacetFilterInput } from '@src/types.generated';
|
||||
import { EntityType, FacetFilterInput } from '@src/types.generated';
|
||||
|
||||
interface Props {
|
||||
field: FilterField;
|
||||
@ -14,6 +14,7 @@ interface Props {
|
||||
children?: any;
|
||||
className?: string;
|
||||
manuallyUpdateFilters?: (newValues: FacetFilterInput[]) => void;
|
||||
aggregationsEntityTypes?: Array<EntityType>;
|
||||
}
|
||||
|
||||
export default function ValueSelector({
|
||||
@ -24,6 +25,7 @@ export default function ValueSelector({
|
||||
children,
|
||||
className,
|
||||
manuallyUpdateFilters,
|
||||
aggregationsEntityTypes,
|
||||
}: Props) {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
@ -53,6 +55,7 @@ export default function ValueSelector({
|
||||
includeCount
|
||||
className={className}
|
||||
manuallyUpdateFilters={onManuallyUpdateFilters}
|
||||
aggregationsEntityTypes={aggregationsEntityTypes}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
|
||||
@ -48,7 +48,17 @@ export const mapFilterCountsToZero = (options: FilterValueOption[]) => {
|
||||
*
|
||||
* TODO: Determine if we need to provide an option context that would help with filtering.
|
||||
*/
|
||||
export const useLoadAggregationOptions = (field: FilterField, visible: boolean, includeCounts: boolean) => {
|
||||
export const useLoadAggregationOptions = ({
|
||||
field,
|
||||
visible,
|
||||
includeCounts,
|
||||
aggregationsEntityTypes,
|
||||
}: {
|
||||
field: FilterField;
|
||||
visible: boolean;
|
||||
includeCounts: boolean;
|
||||
aggregationsEntityTypes?: Array<EntityType>;
|
||||
}) => {
|
||||
const { entityFilters, query, orFilters, viewUrn } = useGetSearchQueryInputs(
|
||||
useMemo(() => [field.field], [field.field]),
|
||||
);
|
||||
@ -61,7 +71,7 @@ export const useLoadAggregationOptions = (field: FilterField, visible: boolean,
|
||||
searchFlags: {
|
||||
maxAggValues: MAX_AGGREGATION_COUNT,
|
||||
},
|
||||
types: field.field === ENTITY_FILTER_NAME ? null : entityFilters,
|
||||
types: aggregationsEntityTypes || (field.field === ENTITY_FILTER_NAME ? null : entityFilters),
|
||||
orFilters,
|
||||
viewUrn,
|
||||
},
|
||||
|
||||
108
datahub-web-react/src/app/sharedV2/filters/Filter.tsx
Normal file
108
datahub-web-react/src/app/sharedV2/filters/Filter.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import { Button } from 'antd';
|
||||
import { CaretDown } from 'phosphor-react';
|
||||
import React from 'react';
|
||||
import styled, { CSSProperties } from 'styled-components';
|
||||
|
||||
import { Pill } from '@src/alchemy-components';
|
||||
import { IconWrapper } from '@src/app/searchV2/filters/SearchFilterView';
|
||||
import { FilterPredicate } from '@src/app/searchV2/filters/types';
|
||||
import useFilterDropdown from '@src/app/searchV2/filters/useSearchFilterDropdown';
|
||||
import { getFilterDropdownIcon, useFilterDisplayName } from '@src/app/searchV2/filters/utils';
|
||||
import ValueSelector from '@src/app/searchV2/filters/value/ValueSelector';
|
||||
import { formatNumber } from '@src/app/shared/formatNumber';
|
||||
import { EntityType, FacetFilterInput, FacetMetadata } from '@src/types.generated';
|
||||
|
||||
export type FilterLabels = {
|
||||
[key: string]: {
|
||||
displayName: string;
|
||||
icon?: React.ReactElement;
|
||||
};
|
||||
};
|
||||
|
||||
interface Props {
|
||||
filter: FacetMetadata;
|
||||
activeFilters: FacetFilterInput[];
|
||||
onChangeFilters: (newFilters: FacetFilterInput[]) => void;
|
||||
filterPredicates: FilterPredicate[];
|
||||
labelStyle?: CSSProperties;
|
||||
customFilterLabels?: FilterLabels;
|
||||
aggregationsEntityTypes?: Array<EntityType>;
|
||||
}
|
||||
|
||||
const FilterLabel = styled(Button)<{ $isActive: boolean }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
gap: 8px;
|
||||
margin: 0 4px;
|
||||
border: 1px solid #ebecf0;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: inherit;
|
||||
border-color: #ebecf0;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.$isActive &&
|
||||
`
|
||||
color: #374066;
|
||||
font-weight: 600;
|
||||
`}
|
||||
`;
|
||||
|
||||
export default function Filter({
|
||||
filter,
|
||||
filterPredicates,
|
||||
activeFilters,
|
||||
onChangeFilters,
|
||||
labelStyle,
|
||||
customFilterLabels,
|
||||
aggregationsEntityTypes,
|
||||
}: Props) {
|
||||
const { finalAggregations, updateFilters, numActiveFilters } = useFilterDropdown({
|
||||
filter,
|
||||
activeFilters,
|
||||
onChangeFilters,
|
||||
aggregationsEntityTypes,
|
||||
});
|
||||
|
||||
const currentFilterPredicate = filterPredicates?.find((obj) => obj.field.field === filter.field) as FilterPredicate;
|
||||
|
||||
// TODO: Have config for the value labels as well
|
||||
const labelConfig = customFilterLabels?.[filter.field];
|
||||
|
||||
const filterIcon = labelConfig ? labelConfig.icon : getFilterDropdownIcon(filter.field);
|
||||
const entityFilterName = useFilterDisplayName(filter, currentFilterPredicate?.field?.displayName);
|
||||
const displayName = labelConfig ? labelConfig.displayName : entityFilterName;
|
||||
|
||||
return (
|
||||
<ValueSelector
|
||||
field={currentFilterPredicate?.field}
|
||||
values={currentFilterPredicate?.values}
|
||||
defaultOptions={finalAggregations}
|
||||
onChangeValues={updateFilters}
|
||||
aggregationsEntityTypes={aggregationsEntityTypes}
|
||||
>
|
||||
<FilterLabel
|
||||
$isActive={!!numActiveFilters}
|
||||
style={labelStyle}
|
||||
data-testid={`filter-dropdown-${displayName?.replace(/\s/g, '-')}`}
|
||||
>
|
||||
{filterIcon && <IconWrapper>{filterIcon}</IconWrapper>}
|
||||
{displayName} {!!numActiveFilters && <Pill size="xs" label={formatNumber(numActiveFilters)} />}
|
||||
<CaretDown style={{ fontSize: '14px', height: '14px' }} />
|
||||
</FilterLabel>
|
||||
</ValueSelector>
|
||||
);
|
||||
}
|
||||
69
datahub-web-react/src/app/sharedV2/filters/FilterSection.tsx
Normal file
69
datahub-web-react/src/app/sharedV2/filters/FilterSection.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Filter, { FilterLabels } from '@app/sharedV2/filters/Filter';
|
||||
import FiltersLoadingSection from '@src/app/searchV2/filters/SearchFiltersLoadingSection';
|
||||
import { FilterPredicate } from '@src/app/searchV2/filters/types';
|
||||
import { convertToAvailableFilterPredictes } from '@src/app/searchV2/filters/utils';
|
||||
import { EntityType, FacetFilterInput, FacetMetadata } from '@src/types.generated';
|
||||
|
||||
const Section = styled.div<{ removePadding?: boolean }>`
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
loading: boolean;
|
||||
availableFilters: FacetMetadata[];
|
||||
activeFilters: FacetFilterInput[];
|
||||
onChangeFilters: (newFilters: FacetFilterInput[]) => void;
|
||||
aggregationsEntityTypes?: Array<EntityType>;
|
||||
customFilterLabels?: FilterLabels;
|
||||
noOfLoadingSkeletons?: number;
|
||||
}
|
||||
|
||||
export default function FilterSection({
|
||||
name,
|
||||
loading,
|
||||
availableFilters,
|
||||
activeFilters,
|
||||
onChangeFilters,
|
||||
aggregationsEntityTypes,
|
||||
customFilterLabels,
|
||||
noOfLoadingSkeletons,
|
||||
}: Props) {
|
||||
const [finalAvailableFilters, setFinalAvailableFilters] = useState(availableFilters);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && finalAvailableFilters !== availableFilters) {
|
||||
setFinalAvailableFilters(availableFilters);
|
||||
}
|
||||
}, [availableFilters, loading, finalAvailableFilters]);
|
||||
|
||||
const filterPredicates: FilterPredicate[] = convertToAvailableFilterPredictes(
|
||||
activeFilters,
|
||||
finalAvailableFilters || [],
|
||||
);
|
||||
|
||||
return (
|
||||
<Section id={`${name}-filters-section`} data-testid={`${name}-filters-section`}>
|
||||
{loading && !finalAvailableFilters?.length && (
|
||||
<FiltersLoadingSection noOfLoadingSkeletons={noOfLoadingSkeletons} />
|
||||
)}
|
||||
{finalAvailableFilters?.map((filter) => (
|
||||
<Filter
|
||||
key={filter.field}
|
||||
filter={filter}
|
||||
activeFilters={activeFilters}
|
||||
onChangeFilters={onChangeFilters}
|
||||
filterPredicates={filterPredicates}
|
||||
customFilterLabels={customFilterLabels}
|
||||
aggregationsEntityTypes={aggregationsEntityTypes}
|
||||
/>
|
||||
))}
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
import * as QueryString from 'query-string';
|
||||
|
||||
import filtersToQueryStringParams from '@app/searchV2/utils/filtersToQueryStringParams';
|
||||
import { FacetFilterInput } from '@src/types.generated';
|
||||
|
||||
export const navigateWithFilters = ({
|
||||
filters,
|
||||
history,
|
||||
location,
|
||||
}: {
|
||||
filters?: Array<FacetFilterInput>;
|
||||
history: any;
|
||||
location: any;
|
||||
}) => {
|
||||
const search = QueryString.stringify(
|
||||
{
|
||||
...filtersToQueryStringParams(filters || []),
|
||||
},
|
||||
{ arrayFormat: 'comma' },
|
||||
);
|
||||
|
||||
history.replace({
|
||||
pathname: location.pathname, // Keep the current pathname
|
||||
search,
|
||||
});
|
||||
};
|
||||
@ -1148,6 +1148,11 @@ fragment facetFields on FacetMetadata {
|
||||
...parentDomainsFields
|
||||
}
|
||||
}
|
||||
... on Dataset {
|
||||
platform {
|
||||
...platformFields
|
||||
}
|
||||
}
|
||||
... on Container {
|
||||
platform {
|
||||
...platformFields
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user