From 5a76d0f3bf404c4385c5c175b975c01304093cfa Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Wed, 18 Sep 2024 16:30:10 +0300 Subject: [PATCH 1/9] feat(playground): show custom granularities for time dimensions in QueryBuilder --- .../src/QueryBuilderV2/components/TimeListMember.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx index d0794894f33b0..8ce50f90a9850 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx @@ -66,8 +66,12 @@ export function TimeListMember(props: ListMemberProps) { // @ts-ignore const description = member.description; const isTimestampSelected = isSelected(); - const isGranularitySelectedList = GRANULARITIES.map((granularity) => isSelected(granularity)); - const selectedGranularity = GRANULARITIES.find((granularity) => isSelected(granularity)); + + const memberGranularities = (member.type === 'time' && member.granularities) ? + member.granularities.map(g => g.name).concat(GRANULARITIES) : + GRANULARITIES; + const isGranularitySelectedList = memberGranularities.map((granularity) => isSelected(granularity)); + const selectedGranularity = memberGranularities.find((granularity) => isSelected(granularity)); // const isGranularitySelected = !!isGranularitySelectedList.find((gran) => gran); open = isCompact ? false : open; @@ -162,7 +166,7 @@ export function TimeListMember(props: ListMemberProps) { value ) : null} - {GRANULARITIES.map((granularity, i) => { + {memberGranularities.map((granularity, i) => { return open && !isCompact ? ( Date: Tue, 24 Sep 2024 13:39:06 +0300 Subject: [PATCH 2/9] =?UTF-8?q?split=20predefined=20and=C2=A0custom=20gran?= =?UTF-8?q?ularities=20into=C2=A0separate=20lists=20to=C2=A0be=20able=20to?= =?UTF-8?q?=C2=A0use=20different=20styles/icons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/TimeListMember.tsx | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx index 8ce50f90a9850..799ea400751b9 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx @@ -7,6 +7,7 @@ import { TimeIcon, CalendarIcon, TooltipProvider, + CubeIcon, } from '@cube-dev/ui-kit'; import { Cube, TCubeDimension, TimeDimensionGranularity } from '@cubejs-client/core'; @@ -32,7 +33,7 @@ interface ListMemberProps { onToggleDataRange?: (name: string) => void; } -const GRANULARITIES: TimeDimensionGranularity[] = [ +const PREDEFINED_GRANULARITIES: TimeDimensionGranularity[] = [ 'second', 'minute', 'hour', @@ -67,12 +68,16 @@ export function TimeListMember(props: ListMemberProps) { const description = member.description; const isTimestampSelected = isSelected(); + const customGranularities = (member.type === 'time' && member.granularities) ? + member.granularities.map(g => g.name) : []; const memberGranularities = (member.type === 'time' && member.granularities) ? - member.granularities.map(g => g.name).concat(GRANULARITIES) : - GRANULARITIES; - const isGranularitySelectedList = memberGranularities.map((granularity) => isSelected(granularity)); + member.granularities.map(g => g.name).concat(PREDEFINED_GRANULARITIES) : + PREDEFINED_GRANULARITIES; + const isGranularitySelectedMap = {}; + memberGranularities.forEach((granularity) => { + isGranularitySelectedMap[granularity] = isSelected(granularity) + }); const selectedGranularity = memberGranularities.find((granularity) => isSelected(granularity)); - // const isGranularitySelected = !!isGranularitySelectedList.find((gran) => gran); open = isCompact ? false : open; @@ -134,6 +139,26 @@ export function TimeListMember(props: ListMemberProps) { ); + const granularityItems = (items, icon) => { + if (!open || isCompact) { + return null; + } + + return items.map((granularity, i) => ( { + onGranularityToggle(member.name, granularity); + setOpen(false); + }} + > + {granularity} + + )); + } + return ( <> {hasOverflow ? ( @@ -166,22 +191,9 @@ export function TimeListMember(props: ListMemberProps) { value ) : null} - {memberGranularities.map((granularity, i) => { - return open && !isCompact ? ( - } - data-member="timeDimension" - isSelected={isGranularitySelectedList[i]} - onPress={() => { - onGranularityToggle(member.name, granularity); - setOpen(false); - }} - > - {granularity} - - ) : null; - })} + {/* TODO: Add special icon for custom granularities and use it here */} + {granularityItems(customGranularities, )} + {granularityItems(PREDEFINED_GRANULARITIES, )} ) : null} From e357678e8694059ecd71c3ab61041fdafc3c2044 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Tue, 24 Sep 2024 23:25:15 +0300 Subject: [PATCH 3/9] update CartesianChart with custom interval labeling support --- .../QueryBuilderV2/components/ChartRenderer.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/cubejs-playground/src/QueryBuilderV2/components/ChartRenderer.tsx b/packages/cubejs-playground/src/QueryBuilderV2/components/ChartRenderer.tsx index 4414802aad0f7..daaa2487dc44c 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/components/ChartRenderer.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/components/ChartRenderer.tsx @@ -1,4 +1,10 @@ -import { ChartType, TimeDimensionGranularity } from '@cubejs-client/core'; +import { + ChartType, + TimeDimensionGranularity, + granularityFor, + minGranularityForIntervals, + isPredefinedGranularity +} from '@cubejs-client/core'; import { UseCubeQueryResult } from '@cubejs-client/react'; import { Skeleton, Tag, tasty } from '@cube-dev/ui-kit'; import formatDate from 'date-fns/format'; @@ -113,7 +119,12 @@ function CartesianChart({ return (key as string).split('.').length === 3; } ) as string; - const granularity = granularityField?.split('.')[2]; + let granularity = granularityField?.split('.')[2]; + + if (!isPredefinedGranularity(granularity)) { + const granularityInfo = resultSet?.loadResponse.results[0].annotation.timeDimensions[granularityField]?.granularity; + granularity = minGranularityForIntervals(granularityInfo.interval, granularityInfo.offset || granularityFor(granularityInfo.origin)); + } const formatDate = useMemo(() => { if (dateFormat) { From b6941966e93e8e87dac767cf303fe8d7ca6a5b65 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Mon, 7 Oct 2024 19:51:25 +0300 Subject: [PATCH 4/9] =?UTF-8?q?update=20cube-dev/ui-kit=20to=C2=A0latest?= =?UTF-8?q?=20and=C2=A0update=20custom=20granularity=20icon=20in=C2=A0pg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cubejs-playground/package.json | 2 +- .../src/QueryBuilderV2/components/TimeListMember.tsx | 5 ++--- yarn.lock | 8 ++++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/cubejs-playground/package.json b/packages/cubejs-playground/package.json index 6853f0bd09aee..05e24f20a7f2e 100644 --- a/packages/cubejs-playground/package.json +++ b/packages/cubejs-playground/package.json @@ -65,7 +65,7 @@ "devDependencies": { "@ant-design/compatible": "^1.0.1", "@ant-design/icons": "^5.3.5", - "@cube-dev/ui-kit": "0.37.3", + "@cube-dev/ui-kit": "0.37.4", "@cubejs-client/core": "^0.36.4", "@cubejs-client/react": "^0.36.4", "@types/flexsearch": "^0.7.3", diff --git a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx index 799ea400751b9..3be5c9675239b 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx @@ -6,8 +6,8 @@ import { Text, TimeIcon, CalendarIcon, + CalendarEditIcon, TooltipProvider, - CubeIcon, } from '@cube-dev/ui-kit'; import { Cube, TCubeDimension, TimeDimensionGranularity } from '@cubejs-client/core'; @@ -191,8 +191,7 @@ export function TimeListMember(props: ListMemberProps) { value ) : null} - {/* TODO: Add special icon for custom granularities and use it here */} - {granularityItems(customGranularities, )} + {granularityItems(customGranularities, )} {granularityItems(PREDEFINED_GRANULARITIES, )} ) : null} diff --git a/yarn.lock b/yarn.lock index 2fd122919b09e..1e138d2493137 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4242,10 +4242,10 @@ resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz#b6c75a56a1947cc916ea058772d666a2c8932f31" integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA== -"@cube-dev/ui-kit@0.37.3": - version "0.37.3" - resolved "https://registry.yarnpkg.com/@cube-dev/ui-kit/-/ui-kit-0.37.3.tgz#cb0bbba199fe63e692ee1b163f7473e6dd0eb9e9" - integrity sha512-7VJXTm+Z9MYdCErzssb2LkkyF3xzAtrfiD1JkJVX+M/Pc6is3JHLp0MQWBOe859ue4bj6AobHaTZcGB4mpxesg== +"@cube-dev/ui-kit@0.37.4": + version "0.37.4" + resolved "https://registry.yarnpkg.com/@cube-dev/ui-kit/-/ui-kit-0.37.4.tgz#5e79a6875cd20ce24db401aecb594c9278f71313" + integrity sha512-F7P+XAL5ZO1pKauRkeaVxQc0iBx05urKHfZbSvq5Pubavif8HWMBce22rGQ/N7N9+GnTFt0Wt1zq+FEdJsvBUA== dependencies: "@ant-design/icons" "^5.3.4" "@internationalized/date" "^3.5.2" From 4d55bfe400ee722b4d2a2c3fb26be5d0d724044c Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Tue, 8 Oct 2024 13:07:00 +0300 Subject: [PATCH 5/9] Code polish --- .../QueryBuilderV2/components/ChartRenderer.tsx | 12 +++++++++--- .../QueryBuilderV2/components/TimeListMember.tsx | 15 +++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/cubejs-playground/src/QueryBuilderV2/components/ChartRenderer.tsx b/packages/cubejs-playground/src/QueryBuilderV2/components/ChartRenderer.tsx index daaa2487dc44c..16d22b73ece54 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/components/ChartRenderer.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/components/ChartRenderer.tsx @@ -3,7 +3,7 @@ import { TimeDimensionGranularity, granularityFor, minGranularityForIntervals, - isPredefinedGranularity + isPredefinedGranularity, } from '@cubejs-client/core'; import { UseCubeQueryResult } from '@cubejs-client/react'; import { Skeleton, Tag, tasty } from '@cube-dev/ui-kit'; @@ -122,8 +122,14 @@ function CartesianChart({ let granularity = granularityField?.split('.')[2]; if (!isPredefinedGranularity(granularity)) { - const granularityInfo = resultSet?.loadResponse.results[0].annotation.timeDimensions[granularityField]?.granularity; - granularity = minGranularityForIntervals(granularityInfo.interval, granularityInfo.offset || granularityFor(granularityInfo.origin)); + const granularityInfo = + resultSet?.loadResponse.results[0]?.annotation.timeDimensions[granularityField]?.granularity; + if (granularityInfo) { + granularity = minGranularityForIntervals( + granularityInfo.interval, + granularityInfo.offset || granularityFor(granularityInfo.origin) + ); + } } const formatDate = useMemo(() => { diff --git a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx index 3be5c9675239b..822389189c22f 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx @@ -68,14 +68,12 @@ export function TimeListMember(props: ListMemberProps) { const description = member.description; const isTimestampSelected = isSelected(); - const customGranularities = (member.type === 'time' && member.granularities) ? - member.granularities.map(g => g.name) : []; - const memberGranularities = (member.type === 'time' && member.granularities) ? - member.granularities.map(g => g.name).concat(PREDEFINED_GRANULARITIES) : - PREDEFINED_GRANULARITIES; + const customGranularities = + member.type === 'time' && member.granularities ? member.granularities.map((g) => g.name) : []; + const memberGranularities = customGranularities.concat(PREDEFINED_GRANULARITIES); const isGranularitySelectedMap = {}; memberGranularities.forEach((granularity) => { - isGranularitySelectedMap[granularity] = isSelected(granularity) + isGranularitySelectedMap[granularity] = isSelected(granularity); }); const selectedGranularity = memberGranularities.find((granularity) => isSelected(granularity)); @@ -144,7 +142,8 @@ export function TimeListMember(props: ListMemberProps) { return null; } - return items.map((granularity, i) => ( ( + {granularity} )); - } + }; return ( <> From c8cad3de59b6b6dc570800cd5570cd8218d694d2 Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Tue, 8 Oct 2024 19:40:26 +0300 Subject: [PATCH 6/9] sync --- .../src/QueryBuilderV2/QueryBuilderExtras.tsx | 2 +- .../components/ChartRenderer.tsx | 21 +--- .../components/GranularityListMember.tsx | 56 ++++++++++ .../QueryBuilderV2/components/ListMember.tsx | 12 ++- .../components/SidePanelCubeItem.tsx | 10 +- .../components/TimeListMember.tsx | 101 +++++++++--------- .../utils/format-date-by-granularity.tsx | 6 +- .../src/QueryBuilderV2/utils/index.ts | 1 + .../src/QueryBuilderV2/utils/titleize.ts | 57 ++++++++++ 9 files changed, 187 insertions(+), 79 deletions(-) create mode 100644 packages/cubejs-playground/src/QueryBuilderV2/components/GranularityListMember.tsx create mode 100644 packages/cubejs-playground/src/QueryBuilderV2/utils/titleize.ts diff --git a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderExtras.tsx b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderExtras.tsx index bfb57f39f2c94..cba6cfab5966a 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderExtras.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderExtras.tsx @@ -53,7 +53,7 @@ const LIMIT_OPTIONS: { key: number; label: string }[] = [ { key: 1000, label: '1,000' }, { key: 5000, label: '5,000 (Default)' }, { key: 50000, label: '50,000 (Max)' }, -]; +] as const; const LIMIT_OPTION_VALUES = LIMIT_OPTIONS.map((option) => option.key) as number[]; function timezoneByName(name: string) { diff --git a/packages/cubejs-playground/src/QueryBuilderV2/components/ChartRenderer.tsx b/packages/cubejs-playground/src/QueryBuilderV2/components/ChartRenderer.tsx index 16d22b73ece54..e2a7eee5906b3 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/components/ChartRenderer.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/components/ChartRenderer.tsx @@ -7,7 +7,6 @@ import { } from '@cubejs-client/core'; import { UseCubeQueryResult } from '@cubejs-client/react'; import { Skeleton, Tag, tasty } from '@cube-dev/ui-kit'; -import formatDate from 'date-fns/format'; import { ComponentType, memo, useCallback, useMemo } from 'react'; import { Col, Row, Statistic, Table } from 'antd'; import { @@ -34,28 +33,10 @@ import { getChartColorByIndex, getChartSolidColorByIndex, } from '../utils/chart-colors'; +import { formatDateByGranularity, formatDateByPattern } from '../utils/index'; import { LocalError } from './LocalError'; -const FORMAT_MAP = { - second: 'HH:mm:ss, yyyy-LL-dd', - minute: 'HH:mm, yyyy-LL-dd', - hour: 'HH:00, yyyy-LL-dd', - day: 'yyyy-LL-dd', - week: "'W'w yyyy-LL-dd", - month: 'LLL yyyy', - quarter: 'QQQ yyyy', - year: 'yyyy', -}; - -export function formatDateByGranularity(timestamp: Date, granularity?: TimeDimensionGranularity) { - return formatDate(timestamp, FORMAT_MAP[granularity ?? 'second']); -} - -export function formatDateByPattern(timestamp: Date, format?: string) { - return formatDate(timestamp, format ?? FORMAT_MAP['second']); -} - function CustomDot(props: any) { const { cx, cy, fill } = props; diff --git a/packages/cubejs-playground/src/QueryBuilderV2/components/GranularityListMember.tsx b/packages/cubejs-playground/src/QueryBuilderV2/components/GranularityListMember.tsx new file mode 100644 index 0000000000000..b9f356ca8d99f --- /dev/null +++ b/packages/cubejs-playground/src/QueryBuilderV2/components/GranularityListMember.tsx @@ -0,0 +1,56 @@ +import { CalendarEditIcon, CalendarIcon, Text, TooltipProvider } from '@cube-dev/ui-kit'; +import { useRef } from 'react'; + +import { useHasOverflow } from '../hooks/index'; +import { titleize } from '../utils/index'; + +import { ListMemberButton } from './ListMemberButton'; + +export interface GranularityListMemberProps { + name: string; + title?: string; + isCustom?: boolean; + isSelected: boolean; + onToggle: () => void; +} + +export function GranularityListMember(props: GranularityListMemberProps) { + const { name, title, isCustom, isSelected, onToggle } = props; + const textRef = useRef(null); + + const hasOverflow = useHasOverflow(textRef); + const isAutoTitle = titleize(name) === title; + + const button = ( + : } + data-member="timeDimension" + isSelected={isSelected} + onPress={onToggle} + > + + {name} + + + ); + + if (hasOverflow || (!isAutoTitle && isCustom)) { + return ( + + {name} +
+ {title} + + } + delay={1000} + placement="right" + > + {button} +
+ ); + } else { + return button; + } +} diff --git a/packages/cubejs-playground/src/QueryBuilderV2/components/ListMember.tsx b/packages/cubejs-playground/src/QueryBuilderV2/components/ListMember.tsx index 3c2db7af09d8a..bce1d54ef356a 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/components/ListMember.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/components/ListMember.tsx @@ -11,7 +11,7 @@ import { import { TCubeMeasure, TCubeDimension, TCubeSegment, Cube, MemberType } from '@cubejs-client/core'; import { PlusOutlined } from '@ant-design/icons'; -import { getTypeIcon } from '../utils'; +import { getTypeIcon, titleize } from '../utils'; import { PrimaryKeyIcon } from '../icons/PrimaryKeyIcon'; import { NonPublicIcon } from '../icons/NonPublicIcon'; import { ItemInfoIcon } from '../icons/ItemInfoIcon'; @@ -60,6 +60,8 @@ export function ListMember(props: ListMemberProps) { const description = member.description; const hasOverflow = useHasOverflow(textRef); + const isAutoTitle = titleize(member.name) === title; + const button = ( ); - return hasOverflow ? ( + return hasOverflow || !isAutoTitle ? ( - {name} + + {name} + +
+ {title} } delay={1000} diff --git a/packages/cubejs-playground/src/QueryBuilderV2/components/SidePanelCubeItem.tsx b/packages/cubejs-playground/src/QueryBuilderV2/components/SidePanelCubeItem.tsx index e0988049a9699..32be9da44b9a1 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/components/SidePanelCubeItem.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/components/SidePanelCubeItem.tsx @@ -16,6 +16,7 @@ import { ArrowIcon } from '../icons/ArrowIcon'; import { NonPublicIcon } from '../icons/NonPublicIcon'; import { ItemInfoIcon } from '../icons/ItemInfoIcon'; import { useHasOverflow, useFilteredMembers } from '../hooks'; +import { titleize } from '../utils/index'; import { ListMember } from './ListMember'; import { TimeListMember } from './TimeListMember'; @@ -366,6 +367,7 @@ export function SidePanelCubeItem({ }, [segments.join(','), query?.segments?.join(','), showMembers, mode, meta]); const hasOverflow = useHasOverflow(textRef); + const isAutoTitle = titleize(name) === title; const noVisibleMembers = !dimensions.length && !measures.length && !segments.length; @@ -468,12 +470,16 @@ export function SidePanelCubeItem({ return ( - {hasOverflow ? ( + {hasOverflow || !isAutoTitle ? ( - {name} + + {name} + +
+ {title} } placement="right" diff --git a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx index 822389189c22f..0dc08a5537fb8 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx @@ -1,24 +1,17 @@ -import { useRef, useState } from 'react'; -import { - Action, - Flex, - Space, - Text, - TimeIcon, - CalendarIcon, - CalendarEditIcon, - TooltipProvider, -} from '@cube-dev/ui-kit'; +import { useMemo, useRef, useState } from 'react'; +import { Flex, Space, Text, TimeIcon, TooltipProvider } from '@cube-dev/ui-kit'; import { Cube, TCubeDimension, TimeDimensionGranularity } from '@cubejs-client/core'; +import { GranularityListMember } from '@/modules/playground/components/QueryBuilder/components/GranularityListMember'; + import { ArrowIcon } from '../icons/ArrowIcon'; import { NonPublicIcon } from '../icons/NonPublicIcon'; import { ItemInfoIcon } from '../icons/ItemInfoIcon'; import { useHasOverflow } from '../hooks/has-overflow'; +import { titleize } from '../utils/index'; import { ListMemberButton } from './ListMemberButton'; import { FilterByMemberButton } from './FilterByMemberButton'; -import { MemberBadge } from './Badge'; import { FilteredLabel } from './FilteredLabel'; interface ListMemberProps { @@ -70,8 +63,21 @@ export function TimeListMember(props: ListMemberProps) { const customGranularities = member.type === 'time' && member.granularities ? member.granularities.map((g) => g.name) : []; + const customGranularitiesTitleMap = useMemo(() => { + return ( + member.type === 'time' && + member.granularities?.reduce( + (map, granularity) => { + map[granularity.name] = granularity.title; + + return map; + }, + {} as Record + ) + ); + }, [member.type === 'time' ? member.granularities : null]); const memberGranularities = customGranularities.concat(PREDEFINED_GRANULARITIES); - const isGranularitySelectedMap = {}; + const isGranularitySelectedMap: Record = {}; memberGranularities.forEach((granularity) => { isGranularitySelectedMap[granularity] = isSelected(granularity); }); @@ -80,6 +86,8 @@ export function TimeListMember(props: ListMemberProps) { open = isCompact ? false : open; const hasOverflow = useHasOverflow(textRef); + const isAutoTitle = titleize(member.name) === title; + const button = ( {filterString ? : name} - {(isCompact || !open) && selectedGranularity ? ( - - { - onGranularityToggle(member.name, selectedGranularity); - }} - > - - {selectedGranularity} - - - - ) : null} + {description ? : undefined} @@ -137,30 +130,34 @@ export function TimeListMember(props: ListMemberProps) { ); - const granularityItems = (items, icon) => { - if (!open || isCompact) { - return null; - } - - return items.map((granularity, i) => ( - { - onGranularityToggle(member.name, granularity); - setOpen(false); - }} - > - {granularity} - - )); + const granularityItems = (items: string[], isCustom?: boolean) => { + return items.map((granularity: string) => { + if ( + ((!open || isCompact) && !isGranularitySelectedMap[granularity]) || + !customGranularitiesTitleMap + ) { + return null; + } + + return ( + { + onGranularityToggle(member.name, granularity); + setOpen(false); + }} + /> + ); + }); }; return ( <> - {hasOverflow ? ( + {hasOverflow || !isAutoTitle ? ( @@ -175,7 +172,7 @@ export function TimeListMember(props: ListMemberProps) { ) : ( button )} - {open || isCompact ? ( + {open || isCompact || selectedGranularity ? ( {open && !isCompact ? ( value ) : null} - {granularityItems(customGranularities, )} - {granularityItems(PREDEFINED_GRANULARITIES, )} + {granularityItems(customGranularities, true)} + {granularityItems(PREDEFINED_GRANULARITIES)} ) : null} diff --git a/packages/cubejs-playground/src/QueryBuilderV2/utils/format-date-by-granularity.tsx b/packages/cubejs-playground/src/QueryBuilderV2/utils/format-date-by-granularity.tsx index c7edabfa5f628..7320f6f454e98 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/utils/format-date-by-granularity.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/utils/format-date-by-granularity.tsx @@ -13,7 +13,11 @@ const FORMAT_MAP = { }; export function formatDateByGranularity(timestamp: Date, granularity?: TimeDimensionGranularity) { - return formatDate(timestamp, FORMAT_MAP[granularity ?? 'second']); + return formatDate( + timestamp, + FORMAT_MAP[(granularity as Exclude) ?? 'second'] ?? + FORMAT_MAP['second'] + ); } export function formatDateByPattern(timestamp: Date, format?: string) { diff --git a/packages/cubejs-playground/src/QueryBuilderV2/utils/index.ts b/packages/cubejs-playground/src/QueryBuilderV2/utils/index.ts index d44f70600db87..f36b9e5e25c5b 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/utils/index.ts +++ b/packages/cubejs-playground/src/QueryBuilderV2/utils/index.ts @@ -11,3 +11,4 @@ export * from './use-commit-press'; export * from './uncapitalize'; export * from './uniq-array'; export * from './graphql-converters'; +export * from './titleize'; diff --git a/packages/cubejs-playground/src/QueryBuilderV2/utils/titleize.ts b/packages/cubejs-playground/src/QueryBuilderV2/utils/titleize.ts new file mode 100644 index 0000000000000..cdfb68e78b2fd --- /dev/null +++ b/packages/cubejs-playground/src/QueryBuilderV2/utils/titleize.ts @@ -0,0 +1,57 @@ +const underbar = new RegExp('_', 'g'); +const dot = new RegExp('\\.', 'g'); +const nonTitlecasedWords = [ + 'and', + 'or', + 'nor', + 'a', + 'an', + 'the', + 'so', + 'but', + 'to', + 'of', + 'at', + 'by', + 'from', + 'into', + 'on', + 'onto', + 'off', + 'out', + 'in', + 'over', + 'with', + 'for', +]; + +function capitalize(str: string) { + str = str.toLowerCase(); + + return str.substring(0, 1).toUpperCase() + str.substring(1); +} + +export function titleize(str: string) { + str = str.toLowerCase().replace(underbar, ' ').replace(dot, ' '); + const strArr = str.split(' '); + const j = strArr.length; + let d: string[], l: number; + + for (let i = 0; i < j; i++) { + d = strArr[i].split('-'); + l = d.length; + + for (let k = 0; k < l; k++) { + if (nonTitlecasedWords.indexOf(d[k].toLowerCase()) < 0) { + d[k] = capitalize(d[k]); + } + } + + strArr[i] = d.join('-'); + } + + str = strArr.join(' '); + str = str.substring(0, 1).toUpperCase() + str.substring(1); + + return str; +} From 2f874114108be4a6a572385ed5db5b4cbdc194ab Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Tue, 8 Oct 2024 21:08:16 +0300 Subject: [PATCH 7/9] Update QueryBuilderExtras.tsx --- .../cubejs-playground/src/QueryBuilderV2/QueryBuilderExtras.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderExtras.tsx b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderExtras.tsx index cba6cfab5966a..bfb57f39f2c94 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderExtras.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/QueryBuilderExtras.tsx @@ -53,7 +53,7 @@ const LIMIT_OPTIONS: { key: number; label: string }[] = [ { key: 1000, label: '1,000' }, { key: 5000, label: '5,000 (Default)' }, { key: 50000, label: '50,000 (Max)' }, -] as const; +]; const LIMIT_OPTION_VALUES = LIMIT_OPTIONS.map((option) => option.key) as number[]; function timezoneByName(name: string) { From 1bd4bfcb1115f4adbe4c3c0004763780cd93329d Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Wed, 9 Oct 2024 13:13:54 +0300 Subject: [PATCH 8/9] sync --- .../src/QueryBuilderV2/components/TimeListMember.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx index 0dc08a5537fb8..c103bf5e1b8a1 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx @@ -2,7 +2,7 @@ import { useMemo, useRef, useState } from 'react'; import { Flex, Space, Text, TimeIcon, TooltipProvider } from '@cube-dev/ui-kit'; import { Cube, TCubeDimension, TimeDimensionGranularity } from '@cubejs-client/core'; -import { GranularityListMember } from '@/modules/playground/components/QueryBuilder/components/GranularityListMember'; +import { GranularityListMember } from './GranularityListMember'; import { ArrowIcon } from '../icons/ArrowIcon'; import { NonPublicIcon } from '../icons/NonPublicIcon'; From 8f8caa0e8aab8cd1ab941701e1e971e35671f2df Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Wed, 9 Oct 2024 16:56:49 +0300 Subject: [PATCH 9/9] sync --- .../src/QueryBuilderV2/components/TimeListMember.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx index c103bf5e1b8a1..ca8ecc2035162 100644 --- a/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx +++ b/packages/cubejs-playground/src/QueryBuilderV2/components/TimeListMember.tsx @@ -2,14 +2,13 @@ import { useMemo, useRef, useState } from 'react'; import { Flex, Space, Text, TimeIcon, TooltipProvider } from '@cube-dev/ui-kit'; import { Cube, TCubeDimension, TimeDimensionGranularity } from '@cubejs-client/core'; -import { GranularityListMember } from './GranularityListMember'; - import { ArrowIcon } from '../icons/ArrowIcon'; import { NonPublicIcon } from '../icons/NonPublicIcon'; import { ItemInfoIcon } from '../icons/ItemInfoIcon'; import { useHasOverflow } from '../hooks/has-overflow'; import { titleize } from '../utils/index'; +import { GranularityListMember } from './GranularityListMember'; import { ListMemberButton } from './ListMemberButton'; import { FilterByMemberButton } from './FilterByMemberButton'; import { FilteredLabel } from './FilteredLabel';