From e0d3f6684e1707a9825bbb082708bff3e1ea9c05 Mon Sep 17 00:00:00 2001 From: nategrift Date: Wed, 12 Jul 2023 16:01:48 -0400 Subject: [PATCH 1/6] order lifecycles & persist changes --- .../utils/filtersToQueryNode.test.ts | 2 + .../InsightQuery/utils/filtersToQueryNode.ts | 1 + .../utils/queryNodeToFilter.test.ts | 2 + .../InsightQuery/utils/queryNodeToFilter.ts | 1 + .../nodes/InsightViz/LifecycleToggles.tsx | 110 +++++++++++++++--- frontend/src/queries/schema.json | 6 + frontend/src/queries/schema.ts | 2 + frontend/src/scenes/trends/trendsLogic.ts | 24 +++- frontend/src/types.ts | 1 + posthog/schema.py | 1 + 10 files changed, 131 insertions(+), 19 deletions(-) diff --git a/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.test.ts b/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.test.ts index ab120558a72c5..fba97fa3fc0e9 100644 --- a/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.test.ts +++ b/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.test.ts @@ -514,6 +514,7 @@ describe('filtersToQueryNode', () => { insight: InsightType.LIFECYCLE, shown_as: ShownAsValue.LIFECYCLE, toggledLifecycles: ['new', 'dormant'], + lifecyclesOrder: ['new', 'dormant', 'resurrecting', 'returning'], } const result = filtersToQueryNode(filters) @@ -523,6 +524,7 @@ describe('filtersToQueryNode', () => { lifecycleFilter: { shown_as: ShownAsValue.LIFECYCLE, toggledLifecycles: ['new', 'dormant'], + lifecyclesOrder: ['new', 'dormant', 'resurrecting', 'returning'], }, } expect(result).toEqual(query) diff --git a/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.ts b/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.ts index 620e2625708fd..059cfc015081f 100644 --- a/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.ts +++ b/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.ts @@ -267,6 +267,7 @@ export const filtersToQueryNode = (filters: Partial): InsightQueryNo shown_as: filters.shown_as, toggledLifecycles: filters.toggledLifecycles, show_values_on_series: filters.show_values_on_series, + lifecyclesOrder: filters.lifecyclesOrder, }) } diff --git a/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.test.ts b/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.test.ts index 7b60274cbb7b0..aa5cff65f1b35 100644 --- a/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.test.ts +++ b/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.test.ts @@ -10,6 +10,7 @@ describe('queryNodeToFilter', () => { insight: InsightType.LIFECYCLE, shown_as: ShownAsValue.LIFECYCLE, toggledLifecycles: ['new', 'dormant'], + lifecyclesOrder: ['new', 'dormant', 'resurrecting', 'returning'], } const query: LifecycleQuery = { @@ -17,6 +18,7 @@ describe('queryNodeToFilter', () => { lifecycleFilter: { shown_as: ShownAsValue.LIFECYCLE, toggledLifecycles: ['new', 'dormant'], + lifecyclesOrder: ['new', 'dormant', 'resurrecting', 'returning'], }, series: [], } diff --git a/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts b/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts index 948dbcd18641f..54623bb1fae93 100644 --- a/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts +++ b/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts @@ -123,6 +123,7 @@ export const queryNodeToFilter = (query: InsightQueryNode): Partial if (isLifecycleQuery(query) && isLifecycleFilter(filters)) { filters.toggledLifecycles = query.lifecycleFilter?.toggledLifecycles + filters.lifecyclesOrder = query.lifecycleFilter?.lifecyclesOrder filters.shown_as = query.lifecycleFilter?.shown_as } diff --git a/frontend/src/queries/nodes/InsightViz/LifecycleToggles.tsx b/frontend/src/queries/nodes/InsightViz/LifecycleToggles.tsx index 0c604833e7170..3d95cd2b039ff 100644 --- a/frontend/src/queries/nodes/InsightViz/LifecycleToggles.tsx +++ b/frontend/src/queries/nodes/InsightViz/LifecycleToggles.tsx @@ -1,41 +1,51 @@ import { LifecycleQuery } from '~/queries/schema' import { LifecycleToggle } from '~/types' import { LemonCheckbox, LemonLabel } from '@posthog/lemon-ui' +import { IconDragHandle } from 'lib/lemon-ui/icons' +import { useState, DragEventHandler } from 'react' -const lifecycles: { name: LifecycleToggle; tooltip: string; color: string }[] = [ - { +export type DragAndDropState = { + draggedFrom: number | null + draggedTo: number | null + isDragging: boolean +} + +const lifecycles: { [key: string]: { name: LifecycleToggle; tooltip: string; color: string } } = { + new: { name: 'new', tooltip: 'Users who were first seen on this period and did the activity during the period.', color: 'var(--lifecycle-new)', }, - { + returning: { name: 'returning', tooltip: 'Users who did activity both this and previous period.', color: 'var(--lifecycle-returning)', }, - { + resurrecting: { name: 'resurrecting', tooltip: 'Users who did the activity this period but did not do the activity on the previous period (i.e. were inactive for 1 or more periods).', color: 'var(--lifecycle-resurrecting)', }, - { + dormant: { name: 'dormant', tooltip: 'Users who went dormant on this period, i.e. users who did not do the activity this period but did the activity on the previous period.', color: 'var(--lifecycle-dormant)', }, -] +} type LifecycleTogglesProps = { query: LifecycleQuery setQuery: (node: LifecycleQuery) => void } -const DEFAULT_LIFECYCLE_TOGGLES: LifecycleToggle[] = ['new', 'returning', 'resurrecting', 'dormant'] +const DEFAULT_LIFECYCLE_TOGGLES: LifecycleToggle[] = ['new', 'returning', 'dormant', 'resurrecting'] export function LifecycleToggles({ query, setQuery }: LifecycleTogglesProps): JSX.Element { const toggledLifecycles = query.lifecycleFilter?.toggledLifecycles || DEFAULT_LIFECYCLE_TOGGLES + const lifecyclesOrder = query.lifecycleFilter?.lifecyclesOrder || DEFAULT_LIFECYCLE_TOGGLES + const setToggledLifecycles = (lifecycles: LifecycleToggle[]): void => { setQuery({ ...query, @@ -46,6 +56,16 @@ export function LifecycleToggles({ query, setQuery }: LifecycleTogglesProps): JS }) } + const setLifecyclesOrder = (lifecycles: LifecycleToggle[]): void => { + setQuery({ + ...query, + lifecycleFilter: { + ...query.lifecycleFilter, + lifecyclesOrder: lifecycles, + }, + }) + } + const toggleLifecycle = (name: LifecycleToggle): void => { if (toggledLifecycles.includes(name)) { setToggledLifecycles(toggledLifecycles.filter((n) => n !== name)) @@ -54,17 +74,75 @@ export function LifecycleToggles({ query, setQuery }: LifecycleTogglesProps): JS } } + // basic starting state for holding and handling the drag data + const [dragAndDrop, setDragAndDrop] = useState({ + draggedFrom: null, + draggedTo: null, + isDragging: false, + }) + + const onDragStart: DragEventHandler = (event): void => { + const initialPosition = Number(event.currentTarget.dataset.position) + + setDragAndDrop({ + draggedTo: null, + draggedFrom: initialPosition, + isDragging: true, + }) + } + + const onDragOver: DragEventHandler = (event): void => { + event.preventDefault() + dragAndDrop.draggedTo = Number(event.currentTarget.dataset.position) + } + + const onDrop: DragEventHandler = (): void => { + const draggedFrom = dragAndDrop.draggedFrom + const draggedTo = dragAndDrop.draggedTo + + // valid this is a valid place to move over + if (draggedFrom == null || draggedTo == null) { + return + } + + let copiedList = lifecyclesOrder + const itemDragged = copiedList[draggedFrom] + + // remove item and re-add it where we want it + const remainingItems = copiedList.filter((_, index) => index !== draggedFrom) + copiedList = [...remainingItems.slice(0, draggedTo), itemDragged, ...remainingItems.slice(draggedTo)] + + // updated the query, so the order persists. + setLifecyclesOrder(copiedList) + + setDragAndDrop({ + draggedFrom: null, + draggedTo: null, + isDragging: false, + }) + } + return (
- {lifecycles.map((lifecycle) => ( - - toggleLifecycle(lifecycle.name)} - /> - + {lifecyclesOrder.map((key, i) => ( +
+ + + toggleLifecycle(lifecycles[key].name)} + /> + +
))}
) diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index 7df67f4bb3998..e11e375444ee8 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -1298,6 +1298,12 @@ "additionalProperties": false, "description": "`LifecycleFilterType` minus everything inherited from `FilterType`", "properties": { + "lifecyclesOrder": { + "items": { + "$ref": "#/definitions/LifecycleToggle" + }, + "type": "array" + }, "show_values_on_series": { "type": "boolean" }, diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts index 01983ab07cde6..b4c3c428887bf 100644 --- a/frontend/src/queries/schema.ts +++ b/frontend/src/queries/schema.ts @@ -421,6 +421,8 @@ export interface StickinessQuery extends InsightsQueryBase { export type LifecycleFilter = Omit & { /** Lifecycles that have been removed from display are not included in this array */ toggledLifecycles?: LifecycleToggle[] + /** Lifecycles order in how they display */ + lifecyclesOrder?: LifecycleToggle[] } // using everything except what it inherits from FilterType export interface LifecycleQuery extends InsightsQueryBase { diff --git a/frontend/src/scenes/trends/trendsLogic.ts b/frontend/src/scenes/trends/trendsLogic.ts index bc2bd69b2026c..c7bb98bc2aedb 100644 --- a/frontend/src/scenes/trends/trendsLogic.ts +++ b/frontend/src/scenes/trends/trendsLogic.ts @@ -97,8 +97,8 @@ export const trendsLogic = kea([ (filters): number => (filters.events?.length || 0) + (filters.actions?.length || 0), ], indexedResults: [ - (s) => [s.filters, s.results, s.toggledLifecycles], - (filters, _results, toggledLifecycles): IndexedTrendResult[] => { + (s) => [s.filters, s.results, s.toggledLifecycles, s.lifecyclesOrder], + (filters, _results, toggledLifecycles, lifecyclesOrder): IndexedTrendResult[] => { let results = _results || [] results = results.map((result, index) => ({ ...result, seriesIndex: index })) if ( @@ -108,7 +108,12 @@ export const trendsLogic = kea([ ) { results.sort((a, b) => b.aggregated_value - a.aggregated_value) } else if (isLifecycleFilter(filters)) { - results = results.filter((result) => toggledLifecycles.includes(String(result.status))) + results = results + .filter((result) => toggledLifecycles.includes(String(result.status))) + .sort( + (a, b) => + lifecyclesOrder.indexOf(String(b.status)) - lifecyclesOrder.indexOf(String(a.status)) + ) } return results.map((result, index) => ({ ...result, id: index } as IndexedTrendResult)) }, @@ -160,6 +165,19 @@ export const trendsLogic = kea([ } }, ], + lifecyclesOrder: [ + (s) => [s.filters, s.loadedFilters], + (inflightFilters, loadedFilters): string[] => { + const defaultToggleState = ['new', 'resurrecting', 'returning', 'dormant'] + if (isLifecycleFilter(inflightFilters)) { + return inflightFilters.lifecyclesOrder || defaultToggleState + } else if (isLifecycleFilter(loadedFilters)) { + return (loadedFilters as Partial).lifecyclesOrder || defaultToggleState + } else { + return defaultToggleState + } + }, + ], }), listeners(({ actions, values, props }) => ({ diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 8e42ba6f52775..a5cad59323783 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -1715,6 +1715,7 @@ export interface LifecycleFilterType extends FilterType { shown_as?: ShownAsValue show_values_on_series?: boolean toggledLifecycles?: LifecycleToggle[] + lifecyclesOrder?: LifecycleToggle[] } export type LifecycleToggle = 'new' | 'resurrecting' | 'returning' | 'dormant' diff --git a/posthog/schema.py b/posthog/schema.py index 62f83b9ca5e9c..afbedb9bba55a 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -585,6 +585,7 @@ class LifecycleFilter(BaseModel): class Config: extra = Extra.forbid + lifecyclesOrder: Optional[List[LifecycleToggle]] = None show_values_on_series: Optional[bool] = None shown_as: Optional[ShownAsValue] = None toggledLifecycles: Optional[List[LifecycleToggle]] = None From b6f327800bd67d2926c52820af3e709c58fde611 Mon Sep 17 00:00:00 2001 From: nategrift Date: Wed, 12 Jul 2023 17:27:08 -0400 Subject: [PATCH 2/6] added better styling to lifecycle dragging --- .../src/queries/nodes/InsightViz/LifecycleToggles.scss | 10 ++++++++++ .../src/queries/nodes/InsightViz/LifecycleToggles.tsx | 10 ++++++++-- frontend/src/scenes/trends/trendsLogic.ts | 8 ++++---- 3 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 frontend/src/queries/nodes/InsightViz/LifecycleToggles.scss diff --git a/frontend/src/queries/nodes/InsightViz/LifecycleToggles.scss b/frontend/src/queries/nodes/InsightViz/LifecycleToggles.scss new file mode 100644 index 0000000000000..b8168c64f3579 --- /dev/null +++ b/frontend/src/queries/nodes/InsightViz/LifecycleToggles.scss @@ -0,0 +1,10 @@ +.LifecycleToggles { + .LifecycleToggles__item--hover { + border: 1px solid var(--border); + padding: 0 0.75rem; + + * { + opacity: 0.6; + } + } +} diff --git a/frontend/src/queries/nodes/InsightViz/LifecycleToggles.tsx b/frontend/src/queries/nodes/InsightViz/LifecycleToggles.tsx index 3d95cd2b039ff..a89dc6c051e60 100644 --- a/frontend/src/queries/nodes/InsightViz/LifecycleToggles.tsx +++ b/frontend/src/queries/nodes/InsightViz/LifecycleToggles.tsx @@ -3,6 +3,7 @@ import { LifecycleToggle } from '~/types' import { LemonCheckbox, LemonLabel } from '@posthog/lemon-ui' import { IconDragHandle } from 'lib/lemon-ui/icons' import { useState, DragEventHandler } from 'react' +import './LifecycleToggles.scss' export type DragAndDropState = { draggedFrom: number | null @@ -93,7 +94,11 @@ export function LifecycleToggles({ query, setQuery }: LifecycleTogglesProps): JS const onDragOver: DragEventHandler = (event): void => { event.preventDefault() - dragAndDrop.draggedTo = Number(event.currentTarget.dataset.position) + + setDragAndDrop({ + ...dragAndDrop, + draggedTo: Number(event.currentTarget.dataset.position), + }) } const onDrop: DragEventHandler = (): void => { @@ -123,7 +128,7 @@ export function LifecycleToggles({ query, setQuery }: LifecycleTogglesProps): JS } return ( -
+
{lifecyclesOrder.map((key, i) => (
diff --git a/frontend/src/scenes/trends/trendsLogic.ts b/frontend/src/scenes/trends/trendsLogic.ts index c7bb98bc2aedb..e3a44ce00f4ba 100644 --- a/frontend/src/scenes/trends/trendsLogic.ts +++ b/frontend/src/scenes/trends/trendsLogic.ts @@ -168,13 +168,13 @@ export const trendsLogic = kea([ lifecyclesOrder: [ (s) => [s.filters, s.loadedFilters], (inflightFilters, loadedFilters): string[] => { - const defaultToggleState = ['new', 'resurrecting', 'returning', 'dormant'] + const defaultOrderedLifecycles = ['new', 'resurrecting', 'returning', 'dormant'] if (isLifecycleFilter(inflightFilters)) { - return inflightFilters.lifecyclesOrder || defaultToggleState + return inflightFilters.lifecyclesOrder || defaultOrderedLifecycles } else if (isLifecycleFilter(loadedFilters)) { - return (loadedFilters as Partial).lifecyclesOrder || defaultToggleState + return (loadedFilters as Partial).lifecyclesOrder || defaultOrderedLifecycles } else { - return defaultToggleState + return defaultOrderedLifecycles } }, ], From 0dfcf687e0c3356a52273db327e1871bdb349a77 Mon Sep 17 00:00:00 2001 From: nategrift Date: Wed, 12 Jul 2023 17:50:25 -0400 Subject: [PATCH 3/6] match posthog styling for lifecycle drag feat --- frontend/src/queries/nodes/InsightViz/LifecycleToggles.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/queries/nodes/InsightViz/LifecycleToggles.scss b/frontend/src/queries/nodes/InsightViz/LifecycleToggles.scss index b8168c64f3579..ca4b57007f402 100644 --- a/frontend/src/queries/nodes/InsightViz/LifecycleToggles.scss +++ b/frontend/src/queries/nodes/InsightViz/LifecycleToggles.scss @@ -1,6 +1,7 @@ .LifecycleToggles { .LifecycleToggles__item--hover { border: 1px solid var(--border); + border-radius: var(--radius); padding: 0 0.75rem; * { From e3d09b93e4fba75180f8f0ca025069d9092c270d Mon Sep 17 00:00:00 2001 From: nategrift Date: Tue, 25 Jul 2023 17:10:05 -0400 Subject: [PATCH 4/6] remove drag-drop --- .../utils/filtersToQueryNode.test.ts | 2 - .../InsightQuery/utils/filtersToQueryNode.ts | 1 - .../utils/queryNodeToFilter.test.ts | 2 - .../InsightQuery/utils/queryNodeToFilter.ts | 1 - .../nodes/InsightViz/LifecycleToggles.scss | 11 -- .../nodes/InsightViz/LifecycleToggles.tsx | 118 +++--------------- frontend/src/queries/schema.json | 6 - frontend/src/queries/schema.ts | 2 - frontend/src/scenes/trends/trendsLogic.ts | 21 +--- frontend/src/types.ts | 1 - posthog/schema.py | 1 - 11 files changed, 22 insertions(+), 144 deletions(-) delete mode 100644 frontend/src/queries/nodes/InsightViz/LifecycleToggles.scss diff --git a/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.test.ts b/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.test.ts index fba97fa3fc0e9..ab120558a72c5 100644 --- a/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.test.ts +++ b/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.test.ts @@ -514,7 +514,6 @@ describe('filtersToQueryNode', () => { insight: InsightType.LIFECYCLE, shown_as: ShownAsValue.LIFECYCLE, toggledLifecycles: ['new', 'dormant'], - lifecyclesOrder: ['new', 'dormant', 'resurrecting', 'returning'], } const result = filtersToQueryNode(filters) @@ -524,7 +523,6 @@ describe('filtersToQueryNode', () => { lifecycleFilter: { shown_as: ShownAsValue.LIFECYCLE, toggledLifecycles: ['new', 'dormant'], - lifecyclesOrder: ['new', 'dormant', 'resurrecting', 'returning'], }, } expect(result).toEqual(query) diff --git a/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.ts b/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.ts index 059cfc015081f..620e2625708fd 100644 --- a/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.ts +++ b/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.ts @@ -267,7 +267,6 @@ export const filtersToQueryNode = (filters: Partial): InsightQueryNo shown_as: filters.shown_as, toggledLifecycles: filters.toggledLifecycles, show_values_on_series: filters.show_values_on_series, - lifecyclesOrder: filters.lifecyclesOrder, }) } diff --git a/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.test.ts b/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.test.ts index aa5cff65f1b35..7b60274cbb7b0 100644 --- a/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.test.ts +++ b/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.test.ts @@ -10,7 +10,6 @@ describe('queryNodeToFilter', () => { insight: InsightType.LIFECYCLE, shown_as: ShownAsValue.LIFECYCLE, toggledLifecycles: ['new', 'dormant'], - lifecyclesOrder: ['new', 'dormant', 'resurrecting', 'returning'], } const query: LifecycleQuery = { @@ -18,7 +17,6 @@ describe('queryNodeToFilter', () => { lifecycleFilter: { shown_as: ShownAsValue.LIFECYCLE, toggledLifecycles: ['new', 'dormant'], - lifecyclesOrder: ['new', 'dormant', 'resurrecting', 'returning'], }, series: [], } diff --git a/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts b/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts index 54623bb1fae93..948dbcd18641f 100644 --- a/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts +++ b/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts @@ -123,7 +123,6 @@ export const queryNodeToFilter = (query: InsightQueryNode): Partial if (isLifecycleQuery(query) && isLifecycleFilter(filters)) { filters.toggledLifecycles = query.lifecycleFilter?.toggledLifecycles - filters.lifecyclesOrder = query.lifecycleFilter?.lifecyclesOrder filters.shown_as = query.lifecycleFilter?.shown_as } diff --git a/frontend/src/queries/nodes/InsightViz/LifecycleToggles.scss b/frontend/src/queries/nodes/InsightViz/LifecycleToggles.scss deleted file mode 100644 index ca4b57007f402..0000000000000 --- a/frontend/src/queries/nodes/InsightViz/LifecycleToggles.scss +++ /dev/null @@ -1,11 +0,0 @@ -.LifecycleToggles { - .LifecycleToggles__item--hover { - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 0 0.75rem; - - * { - opacity: 0.6; - } - } -} diff --git a/frontend/src/queries/nodes/InsightViz/LifecycleToggles.tsx b/frontend/src/queries/nodes/InsightViz/LifecycleToggles.tsx index a89dc6c051e60..0c604833e7170 100644 --- a/frontend/src/queries/nodes/InsightViz/LifecycleToggles.tsx +++ b/frontend/src/queries/nodes/InsightViz/LifecycleToggles.tsx @@ -1,52 +1,41 @@ import { LifecycleQuery } from '~/queries/schema' import { LifecycleToggle } from '~/types' import { LemonCheckbox, LemonLabel } from '@posthog/lemon-ui' -import { IconDragHandle } from 'lib/lemon-ui/icons' -import { useState, DragEventHandler } from 'react' -import './LifecycleToggles.scss' -export type DragAndDropState = { - draggedFrom: number | null - draggedTo: number | null - isDragging: boolean -} - -const lifecycles: { [key: string]: { name: LifecycleToggle; tooltip: string; color: string } } = { - new: { +const lifecycles: { name: LifecycleToggle; tooltip: string; color: string }[] = [ + { name: 'new', tooltip: 'Users who were first seen on this period and did the activity during the period.', color: 'var(--lifecycle-new)', }, - returning: { + { name: 'returning', tooltip: 'Users who did activity both this and previous period.', color: 'var(--lifecycle-returning)', }, - resurrecting: { + { name: 'resurrecting', tooltip: 'Users who did the activity this period but did not do the activity on the previous period (i.e. were inactive for 1 or more periods).', color: 'var(--lifecycle-resurrecting)', }, - dormant: { + { name: 'dormant', tooltip: 'Users who went dormant on this period, i.e. users who did not do the activity this period but did the activity on the previous period.', color: 'var(--lifecycle-dormant)', }, -} +] type LifecycleTogglesProps = { query: LifecycleQuery setQuery: (node: LifecycleQuery) => void } -const DEFAULT_LIFECYCLE_TOGGLES: LifecycleToggle[] = ['new', 'returning', 'dormant', 'resurrecting'] +const DEFAULT_LIFECYCLE_TOGGLES: LifecycleToggle[] = ['new', 'returning', 'resurrecting', 'dormant'] export function LifecycleToggles({ query, setQuery }: LifecycleTogglesProps): JSX.Element { const toggledLifecycles = query.lifecycleFilter?.toggledLifecycles || DEFAULT_LIFECYCLE_TOGGLES - const lifecyclesOrder = query.lifecycleFilter?.lifecyclesOrder || DEFAULT_LIFECYCLE_TOGGLES - const setToggledLifecycles = (lifecycles: LifecycleToggle[]): void => { setQuery({ ...query, @@ -57,16 +46,6 @@ export function LifecycleToggles({ query, setQuery }: LifecycleTogglesProps): JS }) } - const setLifecyclesOrder = (lifecycles: LifecycleToggle[]): void => { - setQuery({ - ...query, - lifecycleFilter: { - ...query.lifecycleFilter, - lifecyclesOrder: lifecycles, - }, - }) - } - const toggleLifecycle = (name: LifecycleToggle): void => { if (toggledLifecycles.includes(name)) { setToggledLifecycles(toggledLifecycles.filter((n) => n !== name)) @@ -75,80 +54,17 @@ export function LifecycleToggles({ query, setQuery }: LifecycleTogglesProps): JS } } - // basic starting state for holding and handling the drag data - const [dragAndDrop, setDragAndDrop] = useState({ - draggedFrom: null, - draggedTo: null, - isDragging: false, - }) - - const onDragStart: DragEventHandler = (event): void => { - const initialPosition = Number(event.currentTarget.dataset.position) - - setDragAndDrop({ - draggedTo: null, - draggedFrom: initialPosition, - isDragging: true, - }) - } - - const onDragOver: DragEventHandler = (event): void => { - event.preventDefault() - - setDragAndDrop({ - ...dragAndDrop, - draggedTo: Number(event.currentTarget.dataset.position), - }) - } - - const onDrop: DragEventHandler = (): void => { - const draggedFrom = dragAndDrop.draggedFrom - const draggedTo = dragAndDrop.draggedTo - - // valid this is a valid place to move over - if (draggedFrom == null || draggedTo == null) { - return - } - - let copiedList = lifecyclesOrder - const itemDragged = copiedList[draggedFrom] - - // remove item and re-add it where we want it - const remainingItems = copiedList.filter((_, index) => index !== draggedFrom) - copiedList = [...remainingItems.slice(0, draggedTo), itemDragged, ...remainingItems.slice(draggedTo)] - - // updated the query, so the order persists. - setLifecyclesOrder(copiedList) - - setDragAndDrop({ - draggedFrom: null, - draggedTo: null, - isDragging: false, - }) - } - return ( -
- {lifecyclesOrder.map((key, i) => ( -
- - - toggleLifecycle(lifecycles[key].name)} - /> - -
+
+ {lifecycles.map((lifecycle) => ( + + toggleLifecycle(lifecycle.name)} + /> + ))}
) diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index e11e375444ee8..7df67f4bb3998 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -1298,12 +1298,6 @@ "additionalProperties": false, "description": "`LifecycleFilterType` minus everything inherited from `FilterType`", "properties": { - "lifecyclesOrder": { - "items": { - "$ref": "#/definitions/LifecycleToggle" - }, - "type": "array" - }, "show_values_on_series": { "type": "boolean" }, diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts index b4c3c428887bf..01983ab07cde6 100644 --- a/frontend/src/queries/schema.ts +++ b/frontend/src/queries/schema.ts @@ -421,8 +421,6 @@ export interface StickinessQuery extends InsightsQueryBase { export type LifecycleFilter = Omit & { /** Lifecycles that have been removed from display are not included in this array */ toggledLifecycles?: LifecycleToggle[] - /** Lifecycles order in how they display */ - lifecyclesOrder?: LifecycleToggle[] } // using everything except what it inherits from FilterType export interface LifecycleQuery extends InsightsQueryBase { diff --git a/frontend/src/scenes/trends/trendsLogic.ts b/frontend/src/scenes/trends/trendsLogic.ts index e3a44ce00f4ba..5f546ff1d6ca6 100644 --- a/frontend/src/scenes/trends/trendsLogic.ts +++ b/frontend/src/scenes/trends/trendsLogic.ts @@ -97,8 +97,9 @@ export const trendsLogic = kea([ (filters): number => (filters.events?.length || 0) + (filters.actions?.length || 0), ], indexedResults: [ - (s) => [s.filters, s.results, s.toggledLifecycles, s.lifecyclesOrder], - (filters, _results, toggledLifecycles, lifecyclesOrder): IndexedTrendResult[] => { + (s) => [s.filters, s.results, s.toggledLifecycles], + (filters, _results, toggledLifecycles): IndexedTrendResult[] => { + const defaultLifecyclesOrder = ['new', 'resurrecting', 'returning', 'dormant'] let results = _results || [] results = results.map((result, index) => ({ ...result, seriesIndex: index })) if ( @@ -112,7 +113,8 @@ export const trendsLogic = kea([ .filter((result) => toggledLifecycles.includes(String(result.status))) .sort( (a, b) => - lifecyclesOrder.indexOf(String(b.status)) - lifecyclesOrder.indexOf(String(a.status)) + defaultLifecyclesOrder.indexOf(String(b.status)) - + defaultLifecyclesOrder.indexOf(String(a.status)) ) } return results.map((result, index) => ({ ...result, id: index } as IndexedTrendResult)) @@ -165,19 +167,6 @@ export const trendsLogic = kea([ } }, ], - lifecyclesOrder: [ - (s) => [s.filters, s.loadedFilters], - (inflightFilters, loadedFilters): string[] => { - const defaultOrderedLifecycles = ['new', 'resurrecting', 'returning', 'dormant'] - if (isLifecycleFilter(inflightFilters)) { - return inflightFilters.lifecyclesOrder || defaultOrderedLifecycles - } else if (isLifecycleFilter(loadedFilters)) { - return (loadedFilters as Partial).lifecyclesOrder || defaultOrderedLifecycles - } else { - return defaultOrderedLifecycles - } - }, - ], }), listeners(({ actions, values, props }) => ({ diff --git a/frontend/src/types.ts b/frontend/src/types.ts index a5cad59323783..8e42ba6f52775 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -1715,7 +1715,6 @@ export interface LifecycleFilterType extends FilterType { shown_as?: ShownAsValue show_values_on_series?: boolean toggledLifecycles?: LifecycleToggle[] - lifecyclesOrder?: LifecycleToggle[] } export type LifecycleToggle = 'new' | 'resurrecting' | 'returning' | 'dormant' diff --git a/posthog/schema.py b/posthog/schema.py index afbedb9bba55a..62f83b9ca5e9c 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -585,7 +585,6 @@ class LifecycleFilter(BaseModel): class Config: extra = Extra.forbid - lifecyclesOrder: Optional[List[LifecycleToggle]] = None show_values_on_series: Optional[bool] = None shown_as: Optional[ShownAsValue] = None toggledLifecycles: Optional[List[LifecycleToggle]] = None From 2650b85bbe3c68d53673296d4d2e88f026d01ee4 Mon Sep 17 00:00:00 2001 From: nategrift Date: Tue, 25 Jul 2023 17:41:06 -0400 Subject: [PATCH 5/6] remove merge conflict error --- frontend/src/scenes/trends/trendsLogic.ts | 205 ---------------------- 1 file changed, 205 deletions(-) delete mode 100644 frontend/src/scenes/trends/trendsLogic.ts diff --git a/frontend/src/scenes/trends/trendsLogic.ts b/frontend/src/scenes/trends/trendsLogic.ts deleted file mode 100644 index 5f546ff1d6ca6..0000000000000 --- a/frontend/src/scenes/trends/trendsLogic.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { actions, connect, kea, key, listeners, path, props, reducers, selectors } from 'kea' -import { dayjs } from 'lib/dayjs' -import api from 'lib/api' -import { insightLogic } from '../insights/insightLogic' -import { - InsightLogicProps, - TrendResult, - ActionFilter, - ChartDisplayType, - TrendsFilterType, - LifecycleFilterType, - StickinessFilterType, - LifecycleToggle, -} from '~/types' -import type { trendsLogicType } from './trendsLogicType' -import { IndexedTrendResult } from 'scenes/trends/types' -import { - isFilterWithDisplay, - isLifecycleFilter, - isStickinessFilter, - isTrendsInsight, - keyForInsightLogicProps, -} from 'scenes/insights/sharedUtils' -import { Noun, groupsModel } from '~/models/groupsModel' -import { isTrendsFilter } from 'scenes/insights/sharedUtils' - -export const trendsLogic = kea([ - props({} as InsightLogicProps), - key(keyForInsightLogicProps('all_trends')), - path((key) => ['scenes', 'trends', 'trendsLogic', key]), - - connect((props: InsightLogicProps) => ({ - values: [ - insightLogic(props), - ['filters as inflightFilters', 'insight', 'insightLoading', 'hiddenLegendKeys', 'localFilters'], - groupsModel, - ['aggregationLabel'], - ], - actions: [insightLogic(props), ['toggleVisibility']], - })), - - actions(() => ({ - setFilters: (filters: Partial, mergeFilters = true) => ({ filters, mergeFilters }), - setDisplay: (display) => ({ display }), - loadMoreBreakdownValues: true, - setBreakdownValuesLoading: (loading: boolean) => ({ loading }), - toggleLifecycle: (lifecycleName: LifecycleToggle) => ({ lifecycleName }), - setTargetAction: (action: ActionFilter) => ({ action }), - setIsFormulaOn: (enabled: boolean) => ({ enabled }), - })), - - reducers({ - targetAction: [ - {} as ActionFilter, - { - setTargetAction: (_, { action }) => action ?? {}, - }, - ], - breakdownValuesLoading: [ - false, - { - setBreakdownValuesLoading: (_, { loading }) => loading, - }, - ], - }), - - selectors({ - filters: [ - (s) => [s.inflightFilters], - ( - inflightFilters - ): Partial | Partial | Partial => - inflightFilters && - (isTrendsFilter(inflightFilters) || - isStickinessFilter(inflightFilters) || - isLifecycleFilter(inflightFilters)) - ? inflightFilters - : {}, - ], - loadedFilters: [ - (s) => [s.insight], - ({ filters }): Partial => - filters && (isFilterWithDisplay(filters) || isLifecycleFilter(filters)) ? filters : {}, - ], - results: [ - (s) => [s.insight], - ({ filters, result }): TrendResult[] => - isTrendsInsight(filters?.insight) && Array.isArray(result) ? result : [], - ], - loadMoreBreakdownUrl: [ - (s) => [s.insight], - ({ filters, next }) => (isTrendsInsight(filters?.insight) ? next : null), - ], - resultsLoading: [(s) => [s.insightLoading], (insightLoading) => insightLoading], - numberOfSeries: [ - (selectors) => [selectors.filters], - (filters): number => (filters.events?.length || 0) + (filters.actions?.length || 0), - ], - indexedResults: [ - (s) => [s.filters, s.results, s.toggledLifecycles], - (filters, _results, toggledLifecycles): IndexedTrendResult[] => { - const defaultLifecyclesOrder = ['new', 'resurrecting', 'returning', 'dormant'] - let results = _results || [] - results = results.map((result, index) => ({ ...result, seriesIndex: index })) - if ( - isFilterWithDisplay(filters) && - (filters.display === ChartDisplayType.ActionsBarValue || - filters.display === ChartDisplayType.ActionsPie) - ) { - results.sort((a, b) => b.aggregated_value - a.aggregated_value) - } else if (isLifecycleFilter(filters)) { - results = results - .filter((result) => toggledLifecycles.includes(String(result.status))) - .sort( - (a, b) => - defaultLifecyclesOrder.indexOf(String(b.status)) - - defaultLifecyclesOrder.indexOf(String(a.status)) - ) - } - return results.map((result, index) => ({ ...result, id: index } as IndexedTrendResult)) - }, - ], - aggregationTargetLabel: [ - (s) => [s.aggregationLabel, s.targetAction], - (aggregationLabel, targetAction): Noun => { - return aggregationLabel(targetAction.math_group_type_index) - }, - ], - incompletenessOffsetFromEnd: [ - (s) => [s.filters, s.insight], - (filters, insight) => { - // Returns negative number of points to paint over starting from end of array - if (insight?.result?.[0]?.days === undefined) { - return 0 - } - const startDate = dayjs().startOf(filters.interval ?? 'd') - const startIndex = insight.result[0].days.findIndex((day: string) => dayjs(day) >= startDate) - - if (startIndex !== undefined && startIndex !== -1) { - return startIndex - insight.result[0].days.length - } else { - return 0 - } - }, - ], - labelGroupType: [ - (s) => [s.filters], - (filters): 'people' | 'none' | number => { - // Find the commonly shared aggregation group index if there is one. - const eventsAndActions = [...(filters.events ?? []), ...(filters.actions ?? [])] - const firstAggregationGroupTypeIndex = eventsAndActions?.[0]?.math_group_type_index - return eventsAndActions.every((eOrA) => eOrA?.math_group_type_index === firstAggregationGroupTypeIndex) - ? firstAggregationGroupTypeIndex ?? 'people' // if undefined, will resolve to 'people' label - : 'none' // mixed group types - }, - ], - toggledLifecycles: [ - (s) => [s.filters, s.loadedFilters], - (inflightFilters, loadedFilters): string[] => { - const defaultToggleState = ['new', 'resurrecting', 'returning', 'dormant'] - if (isLifecycleFilter(inflightFilters)) { - return inflightFilters.toggledLifecycles || defaultToggleState - } else if (isLifecycleFilter(loadedFilters)) { - return (loadedFilters as Partial).toggledLifecycles || defaultToggleState - } else { - return defaultToggleState - } - }, - ], - }), - - listeners(({ actions, values, props }) => ({ - setFilters: async ({ filters, mergeFilters }) => { - insightLogic(props).actions.setFilters(mergeFilters ? { ...values.filters, ...filters } : filters) - }, - setDisplay: async ({ display }) => { - actions.setFilters({ display }, true) - }, - loadMoreBreakdownValues: async () => { - if (!values.loadMoreBreakdownUrl) { - return - } - actions.setBreakdownValuesLoading(true) - - const { filters } = values - const response = await api.get(values.loadMoreBreakdownUrl) - insightLogic(props).actions.setInsight( - { - ...values.insight, - result: [...values.results, ...(response.result ? response.result : [])], - filters: filters, - next: response.next, - }, - {} - ) - actions.setBreakdownValuesLoading(false) - }, - toggleLifecycle: ({ lifecycleName }) => { - const toggledLifecycles = values.toggledLifecycles.includes(lifecycleName) - ? values.toggledLifecycles.filter((s) => s !== lifecycleName) - : [...values.toggledLifecycles, lifecycleName] - actions.setFilters({ toggledLifecycles } as Partial, true) - }, - })), -]) From 4d19dbbf457d2144816913cebb3456fe532f19d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Obermu=CC=88ller?= Date: Wed, 26 Jul 2023 15:12:46 +0200 Subject: [PATCH 6/6] fix order in jest tests --- .../src/scenes/trends/trendsDataLogic.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/src/scenes/trends/trendsDataLogic.test.ts b/frontend/src/scenes/trends/trendsDataLogic.test.ts index 5da842ecedb29..b4e78196b18cd 100644 --- a/frontend/src/scenes/trends/trendsDataLogic.test.ts +++ b/frontend/src/scenes/trends/trendsDataLogic.test.ts @@ -117,24 +117,24 @@ describe('trendsDataLogic', () => { builtDataNodeLogic.actions.loadDataSuccess(insight) }).toMatchValues({ indexedResults: [ - expect.objectContaining({ - count: 35346.0, - status: 'new', - id: 0, - seriesIndex: 0, - }), expect.objectContaining({ count: -50255.0, status: 'dormant', - id: 1, + id: 0, seriesIndex: 1, }), expect.objectContaining({ count: 11612.0, status: 'resurrecting', - id: 2, + id: 1, seriesIndex: 3, }), + expect.objectContaining({ + count: 35346.0, + status: 'new', + id: 2, + seriesIndex: 0, + }), ], }) })