From f0f2401be29b2138ea6de584dd6c652462902b23 Mon Sep 17 00:00:00 2001 From: Andrea Rosci Date: Mon, 11 Nov 2024 17:31:25 +0100 Subject: [PATCH] possible fix --- src/AutoUI/index.tsx | 47 +++++----- src/components/Filters/FilterDescription.tsx | 99 ++------------------ src/components/Filters/SchemaSieve.ts | 89 +++++++++++++++++- 3 files changed, 118 insertions(+), 117 deletions(-) diff --git a/src/AutoUI/index.tsx b/src/AutoUI/index.tsx index 0b208dab..3175db44 100644 --- a/src/AutoUI/index.tsx +++ b/src/AutoUI/index.tsx @@ -72,12 +72,14 @@ import { useAnalyticsContext, } from '@balena/ui-shared-components'; import type { FiltersView } from '../components/Filters'; -import { ajvFilter } from '../components/Filters/SchemaSieve'; +import { + ajvFilter, + convertFilterToHumanReadable, +} from '../components/Filters/SchemaSieve'; import type { Format } from '../components/Widget/utils'; import type { Dictionary } from '../common'; import { defaultFormats } from '../components/Widget/Formats'; -import { listFilterQuery } from './Filters/PersistentFilters'; -import { removeRefSchemeSeparatorsFromFilters } from './Filters/utils'; + const { Box, styled } = Material; const HeaderGrid = styled(Box)(({ theme }) => ({ @@ -183,8 +185,16 @@ export const AutoUI = >({ const history = useHistory(); // Use a flag to make sure table view event is only triggered once (without the tag // it will be triggered whenever the data is updated) - const [shouldTableViewEventBeTriggered, setShouldTableViewEventBeTriggered] = - React.useState(true); + const shouldTableViewEventBeTriggered = React.useRef(true); + const totalItems = React.useMemo( + () => + pagination && 'totalItems' in pagination + ? pagination.totalItems + : Array.isArray(data) + ? data.length + : null, + [pagination, data], + ); const modelRef = React.useRef(modelRaw); // This allows the component to work even if @@ -198,10 +208,7 @@ export const AutoUI = >({ const [filters, setFilters] = React.useState([]); const [sort, setSort] = React.useState | null>( - () => - (getFromLocalStorage(`${model.resource}__sort`) as - | TableSortOptions - | undefined) || null, + () => getFromLocalStorage(`${model.resource}__sort`) || null, ); const [internalPagination, setInternalPagination] = React.useState<{ page: number; @@ -438,28 +445,22 @@ export const AutoUI = >({ }; React.useEffect(() => { - if (!lens || !shouldTableViewEventBeTriggered) { + if (!lens || !shouldTableViewEventBeTriggered || !filters.length) { return; } - const dataCount = Array.isArray(data) ? data.length : null; - const totalItems = pagination?.serverSide - ? pagination.totalItems - : dataCount; + const amplitudeFilter = filters.map((f) => convertFilterToHumanReadable(f)); analytics.webTracker?.track('Resource List View', { lens: lens.slug, resource: model.resource, totalItems, - filters: Object.assign( - {}, - listFilterQuery(removeRefSchemeSeparatorsFromFilters(filters), false), - ), + filters: amplitudeFilter, sort, }); - setShouldTableViewEventBeTriggered(false); - }, [lens, model.resource, pagination, filters, sort, data]); + shouldTableViewEventBeTriggered.current = false; + }, [lens, model.resource, filters, sort, totalItems]); if (loading && data == null) { return ( @@ -551,8 +552,6 @@ export const AutoUI = >({ resource: model.resource, lens: lens.slug, }); - - setShouldTableViewEventBeTriggered(true); }} /> @@ -643,7 +642,9 @@ export const AutoUI = >({ {actionData?.action?.renderer?.({ schema: actionData.schema, affectedEntries: actionData.affectedEntries, - onDone: () => setActionData(undefined), + onDone: () => { + setActionData(undefined); + }, setSelected: $setSelected, })} diff --git a/src/components/Filters/FilterDescription.tsx b/src/components/Filters/FilterDescription.tsx index af29b930..fa19d917 100644 --- a/src/components/Filters/FilterDescription.tsx +++ b/src/components/Filters/FilterDescription.tsx @@ -1,59 +1,8 @@ import type { JSONSchema7 as JSONSchema } from 'json-schema'; import * as React from 'react'; -import { Tag, type TagProps, TagItem } from 'rendition'; -import { - FULL_TEXT_SLUG, - parseFilterDescription, - type FilterDescription as SieveFilterDescription, -} from './SchemaSieve'; -import { isDateTimeFormat } from '../../DataTypes'; -import { format as dateFormat } from 'date-fns'; -import { isJSONSchema } from '../../AutoUI/schemaOps'; -import isEqual from 'lodash/isEqual'; -import { findInObject } from '../../AutoUI/utils'; - -const transformToReadableValue = ( - parsedFilterDescription: SieveFilterDescription, -): string => { - const { schema, value } = parsedFilterDescription; - if (schema && isDateTimeFormat(schema.format)) { - return dateFormat(value, 'PPPppp'); - } - const schemaEnum: JSONSchema['enum'] = findInObject(schema, 'enum'); - const schemaEnumNames: string[] | undefined = findInObject( - schema, - 'enumNames', - ); - if (schemaEnum && schemaEnumNames) { - const index = schemaEnum.findIndex((a) => isEqual(a, value)); - return (schemaEnumNames as string[])[index]; - } - - const oneOf: JSONSchema['oneOf'] = findInObject(schema, 'oneOf'); - if (oneOf) { - const selected = oneOf.find( - (o) => isJSONSchema(o) && isEqual(o.const, value), - ); - - return isJSONSchema(selected) && selected.title ? selected.title : value; - } - - if (typeof value === 'object') { - if (Object.keys(value).length > 1) { - return Object.entries(value) - .map(([key, value]) => { - const property = schema.properties?.[key]; - return isJSONSchema(property) - ? `${property.title ?? key}: ${value}` - : `${key}: ${value}`; - }) - .join(', '); - } - return Object.values(value)[0] as string; - } - - return String(value); -}; +import type { TagItem } from 'rendition'; +import { Tag, type TagProps } from 'rendition'; +import { convertFilterToHumanReadable } from './SchemaSieve'; export interface FilterDescriptionProps extends Omit { filter: JSONSchema; @@ -63,44 +12,10 @@ export const FilterDescription = ({ filter, ...props }: FilterDescriptionProps) => { - const tagProps = React.useMemo(() => { - if (filter.title === FULL_TEXT_SLUG) { - const parsedFilterDescription = parseFilterDescription(filter); - if (!parsedFilterDescription) { - return; - } - return parsedFilterDescription - ? [ - { - name: parsedFilterDescription.field, - operator: 'contains', - value: transformToReadableValue(parsedFilterDescription), - }, - ] - : undefined; - } - - return filter.anyOf - ?.map((f, index) => { - if (!isJSONSchema(f)) { - return; - } - const parsedFilterDescription = parseFilterDescription(f); - if (!parsedFilterDescription) { - return; - } - const value = transformToReadableValue(parsedFilterDescription); - return { - name: - parsedFilterDescription?.schema?.title ?? - parsedFilterDescription.field, - operator: parsedFilterDescription.operator, - value, - prefix: index > 0 ? 'or' : undefined, - }; - }) - .filter((f): f is TagItem => Boolean(f)); - }, [filter]); + const tagProps = React.useMemo( + () => convertFilterToHumanReadable(filter) as TagItem[], + [filter], + ); return tagProps ? : null; }; diff --git a/src/components/Filters/SchemaSieve.ts b/src/components/Filters/SchemaSieve.ts index 14eeb23f..1e0a705d 100644 --- a/src/components/Filters/SchemaSieve.ts +++ b/src/components/Filters/SchemaSieve.ts @@ -1,4 +1,4 @@ -import { +import type { JSONSchema7 as JSONSchema, JSONSchema7Definition as JSONSchemaDefinition, } from 'json-schema'; @@ -12,6 +12,9 @@ import ajvKeywords from 'ajv-keywords'; import addFormats from 'ajv-formats'; import pickBy from 'lodash/pickBy'; import { enqueueSnackbar } from '@balena/ui-shared-components'; +import { format as dateFormat } from 'date-fns'; +import isEqual from 'lodash/isEqual'; +import { findInObject } from '../../AutoUI/utils'; const ajv = new Ajv(); // TODO: remove the any cast as soon as we remove rendition @@ -120,7 +123,7 @@ export const createFilter = ( if (!field || !operator) { return {}; } - const propertySchema = properties[field] as JSONSchema; + const propertySchema = properties[field]; const operators = getAllOperators(propertySchema); const operatorLabel = operators[operator as keyof typeof operators]; const filter = createModelFilter(propertySchema, { @@ -253,3 +256,85 @@ export const parseFilterDescription = ( return; } }; + +const transformToReadableValue = ( + parsedFilterDescription: FilterDescription, +): string => { + const { schema, value } = parsedFilterDescription; + if (schema && isDateTimeFormat(schema.format)) { + return dateFormat(value, 'PPPppp'); + } + const schemaEnum: JSONSchema['enum'] = findInObject(schema, 'enum'); + const schemaEnumNames: string[] | undefined = findInObject( + schema, + 'enumNames', + ); + if (schemaEnum && schemaEnumNames) { + const index = schemaEnum.findIndex((a) => isEqual(a, value)); + return schemaEnumNames[index]; + } + + const oneOf: JSONSchema['oneOf'] = findInObject(schema, 'oneOf'); + if (oneOf) { + const selected = oneOf.find( + (o) => isJSONSchema(o) && isEqual(o.const, value), + ); + + return isJSONSchema(selected) && selected.title ? selected.title : value; + } + + if (typeof value === 'object') { + if (Object.keys(value).length > 1) { + return Object.entries(value) + .map(([key, value]) => { + const property = schema.properties?.[key]; + return isJSONSchema(property) + ? `${property.title ?? key}: ${value}` + : `${key}: ${value}`; + }) + .join(', '); + } + return Object.values(value)[0] as string; + } + + return String(value); +}; + +export const convertFilterToHumanReadable = (filter: JSONSchema) => { + if (filter.title === FULL_TEXT_SLUG) { + const parsedFilterDescription = parseFilterDescription(filter); + if (!parsedFilterDescription) { + return; + } + return parsedFilterDescription + ? [ + { + name: parsedFilterDescription.field, + operator: 'contains', + value: transformToReadableValue(parsedFilterDescription), + }, + ] + : undefined; + } + + return filter.anyOf + ?.map((f, index) => { + if (!isJSONSchema(f)) { + return; + } + const parsedFilterDescription = parseFilterDescription(f); + if (!parsedFilterDescription) { + return; + } + const value = transformToReadableValue(parsedFilterDescription); + return { + name: + parsedFilterDescription?.schema?.title ?? + parsedFilterDescription.field, + operator: parsedFilterDescription.operator, + value, + prefix: index > 0 ? 'or' : undefined, + }; + }) + .filter((f) => Boolean(f)); +};