diff --git a/.github/workflows/ci-e2e.yml b/.github/workflows/ci-e2e.yml index 3b527b691218c..6a8ab0e3962e4 100644 --- a/.github/workflows/ci-e2e.yml +++ b/.github/workflows/ci-e2e.yml @@ -248,6 +248,15 @@ jobs: node-version: 18 cache: pnpm + - name: Restore Cypress binary from cache # actions/setup-node can only cache pnpm's global store + uses: actions/cache@v3 + with: + path: ~/.cache/Cypress + key: ${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} + + - name: Install package.json dependencies with pnpm + run: pnpm install --frozen-lockfile # This is primarily for Cypress + - name: Stop/Start stack with Docker Compose run: | docker compose -f docker-compose.dev.yml down diff --git a/frontend/src/lib/components/PropertyFilters/propertyFilterLogic.ts b/frontend/src/lib/components/PropertyFilters/propertyFilterLogic.ts index 0bc8a2d283685..ceb4ff5bf7ae2 100644 --- a/frontend/src/lib/components/PropertyFilters/propertyFilterLogic.ts +++ b/frontend/src/lib/components/PropertyFilters/propertyFilterLogic.ts @@ -1,23 +1,23 @@ -import { kea } from 'kea' +import { actions, kea, key, listeners, path, props, reducers, selectors } from 'kea' import type { propertyFilterLogicType } from './propertyFilterLogicType' import { AnyPropertyFilter } from '~/types' import { isValidPropertyFilter, parseProperties } from 'lib/components/PropertyFilters/utils' import { PropertyFilterLogicProps } from 'lib/components/PropertyFilters/types' -export const propertyFilterLogic = kea({ - path: (key) => ['lib', 'components', 'PropertyFilters', 'propertyFilterLogic', key], - props: {} as PropertyFilterLogicProps, - key: (props) => props.pageKey, +export const propertyFilterLogic = kea([ + path((key) => ['lib', 'components', 'PropertyFilters', 'propertyFilterLogic', key]), + props({} as PropertyFilterLogicProps), + key((props) => props.pageKey), - actions: () => ({ + actions({ update: true, setFilter: (index: number, property: AnyPropertyFilter) => ({ index, property }), setFilters: (filters: AnyPropertyFilter[]) => ({ filters }), remove: (index: number) => ({ index }), }), - reducers: ({ props }) => ({ + reducers(({ props }) => ({ filters: [ props.propertyFilters ? parseProperties(props.propertyFilters) : ([] as AnyPropertyFilter[]), { @@ -39,9 +39,9 @@ export const propertyFilterLogic = kea({ }, }, ], - }), + })), - listeners: ({ actions, props, values }) => ({ + listeners(({ actions, props, values }) => ({ // Only send update if value is set to something setFilter: ({ property }) => { if (props.sendAllKeyUpdates) { @@ -53,12 +53,11 @@ export const propertyFilterLogic = kea({ remove: () => actions.update(), update: () => { const cleanedFilters = [...values.filters].filter(isValidPropertyFilter) - props.onChange(cleanedFilters) }, - }), + })), - selectors: { + selectors({ filledFilters: [(s) => [s.filters], (filters) => filters.filter(isValidPropertyFilter)], filtersWithNew: [ (s) => [s.filters], @@ -70,5 +69,5 @@ export const propertyFilterLogic = kea({ } }, ], - }, -}) + }), +]) diff --git a/frontend/src/queries/nodes/DataTable/DataTable.tsx b/frontend/src/queries/nodes/DataTable/DataTable.tsx index ac9b58f7cdf63..462ec49bd2cf0 100644 --- a/frontend/src/queries/nodes/DataTable/DataTable.tsx +++ b/frontend/src/queries/nodes/DataTable/DataTable.tsx @@ -12,7 +12,7 @@ import { EventRowActions } from '~/queries/nodes/DataTable/EventRowActions' import { DataTableExport } from '~/queries/nodes/DataTable/DataTableExport' import { Reload } from '~/queries/nodes/DataNode/Reload' import { LoadNext } from '~/queries/nodes/DataNode/LoadNext' -import { renderTitle } from '~/queries/nodes/DataTable/renderTitle' +import { renderColumnMeta } from '~/queries/nodes/DataTable/renderColumnMeta' import { renderColumn } from '~/queries/nodes/DataTable/renderColumn' import { AutoLoad } from '~/queries/nodes/DataNode/AutoLoad' import { dataTableLogic, DataTableLogicProps } from '~/queries/nodes/DataTable/dataTableLogic' @@ -72,7 +72,7 @@ export function DataTable({ query, setQuery, context }: DataTableProps): JSX.Ele const lemonColumns: LemonTableColumn[] = [ ...columns.map((key) => ({ dataIndex: key as any, - title: renderTitle(key, context), + ...renderColumnMeta(key, context), render: function RenderDataTableColumn(_: any, record: EventType) { return renderColumn(key, record, query, setQuery, context) }, @@ -85,6 +85,7 @@ export function DataTable({ query, setQuery, context }: DataTableProps): JSX.Ele render: function RenderMore(_: any, record: EventType) { return }, + width: 0, }, ] : []), diff --git a/frontend/src/queries/nodes/DataTable/dataTableLogic.ts b/frontend/src/queries/nodes/DataTable/dataTableLogic.ts index c58afa3d34cbb..a95aa1ce6e1d0 100644 --- a/frontend/src/queries/nodes/DataTable/dataTableLogic.ts +++ b/frontend/src/queries/nodes/DataTable/dataTableLogic.ts @@ -29,6 +29,7 @@ export const dataTableLogic = kea([ return { kind, columns: columns, + hiddenColumns: [], source, ...sortedKeys({ ...rest, diff --git a/frontend/src/queries/nodes/DataTable/defaults.ts b/frontend/src/queries/nodes/DataTable/defaults.ts index 5ac5dd87ac236..b583702d1ccc1 100644 --- a/frontend/src/queries/nodes/DataTable/defaults.ts +++ b/frontend/src/queries/nodes/DataTable/defaults.ts @@ -16,7 +16,12 @@ export function defaultDataTableColumns(query: DataNode): DataTableColumn[] { } export function defaultsForDataTable(query: DataTableNode, defaultColumns?: DataTableColumn[]): DataTableColumn[] { - return ( + let columns = query.columns ?? (isEventsNode(query.source) ? defaultColumns : null) ?? defaultDataTableColumns(query.source) - ) + + if (query.hiddenColumns) { + columns = columns.filter((column) => !query.hiddenColumns?.includes(column)) + } + + return columns } diff --git a/frontend/src/queries/nodes/DataTable/renderColumnMeta.tsx b/frontend/src/queries/nodes/DataTable/renderColumnMeta.tsx new file mode 100644 index 0000000000000..b4d309ffabf2f --- /dev/null +++ b/frontend/src/queries/nodes/DataTable/renderColumnMeta.tsx @@ -0,0 +1,33 @@ +import { PropertyFilterType } from '~/types' +import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo' +import { QueryContext } from '~/queries/schema' + +export interface ColumnMeta { + title?: JSX.Element | string + width?: number +} + +export function renderColumnMeta(key: string, context?: QueryContext): ColumnMeta { + if (key === 'timestamp') { + return { title: 'Time' } + } else if (key === 'created_at') { + return { title: 'First seen' } + } else if (key === 'event') { + return { title: 'Event' } + } else if (key === 'person') { + return { title: 'Person' } + } else if (key === 'url') { + return { title: 'URL / Screen' } + } else if (key.startsWith('properties.')) { + return { title: } + } else if (key.startsWith('context.columns.')) { + return { title: context?.columns?.[key.substring(16)]?.title ?? key.substring(16).replace('_', ' ') } + } else if (key === 'person.$delete') { + return { title: '', width: 0 } + } else if (key.startsWith('person.properties.')) { + // NOTE: PropertyFilterType.Event is not a mistake. PropertyKeyInfo only knows events vs elements ¯\_(ツ)_/¯ + return { title: } + } else { + return { title: key } + } +} diff --git a/frontend/src/queries/nodes/DataTable/renderTitle.tsx b/frontend/src/queries/nodes/DataTable/renderTitle.tsx deleted file mode 100644 index 39b9e4c4b263e..0000000000000 --- a/frontend/src/queries/nodes/DataTable/renderTitle.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { PropertyFilterType } from '~/types' -import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo' -import { QueryContext } from '~/queries/schema' - -export function renderTitle(key: string, context?: QueryContext): JSX.Element | string { - if (key === 'timestamp') { - return 'Time' - } else if (key === 'created_at') { - return 'First seen' - } else if (key === 'event') { - return 'Event' - } else if (key === 'person') { - return 'Person' - } else if (key === 'url') { - return 'URL / Screen' - } else if (key.startsWith('properties.')) { - return - } else if (key.startsWith('context.columns.')) { - return context?.columns?.[key.substring(16)]?.title ?? key.substring(16).replace('_', ' ') - } else if (key === 'person.$delete') { - return '' - } else if (key.startsWith('person.properties.')) { - // NOTE: PropertyFilterType.Event is not a mistake. PropertyKeyInfo only knows events vs elements ¯\_(ツ)_/¯ - return - } else { - return String(key) - } -} diff --git a/frontend/src/queries/nodes/EventsNode/EventPropertyFilters.tsx b/frontend/src/queries/nodes/EventsNode/EventPropertyFilters.tsx index ec55e7ea392b9..a114221460478 100644 --- a/frontend/src/queries/nodes/EventsNode/EventPropertyFilters.tsx +++ b/frontend/src/queries/nodes/EventsNode/EventPropertyFilters.tsx @@ -10,7 +10,7 @@ interface EventPropertyFiltersProps { let uniqueNode = 0 export function EventPropertyFilters({ query, setQuery }: EventPropertyFiltersProps): JSX.Element { - const [id] = useState(uniqueNode++) + const [id] = useState(() => uniqueNode++) return !query.properties || Array.isArray(query.properties) ? ( ( @@ -20,7 +23,15 @@ export async function query( methodOptions?: ApiMethodOptions ): Promise { if (isEventsNode(query)) { - return await api.get(getEventsEndpoint(query)) + if (!query.before && !query.after) { + const earlyResults = await api.get( + getEventsEndpoint({ ...query, after: now().subtract(EVENTS_DAYS_FIRST_FETCH, 'day').toISOString() }) + ) + if (earlyResults.results.length > 0) { + return earlyResults + } + } + return await api.get(getEventsEndpoint({ after: now().subtract(1, 'year').toISOString(), ...query })) } else if (isPersonsNode(query)) { return await api.get(getPersonsEndpoint(query)) } else if (isLegacyQuery(query)) { @@ -44,7 +55,7 @@ export function getEventsEndpoint(query: EventsNode): string { ...(query.before ? { before: query.before } : {}), ...(query.after ? { after: query.after } : {}), }, - query.limit ?? 3500 + query.limit ?? 100 ) } diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts index 1218631d15f4d..4644ebfd55f38 100644 --- a/frontend/src/queries/schema.ts +++ b/frontend/src/queries/schema.ts @@ -122,6 +122,8 @@ export interface DataTableNode extends Node { source: EventsNode | PersonsNode /** Columns shown in the table */ columns?: DataTableColumn[] + /** Columns that aren't shown in the table, even if in columns */ + hiddenColumns?: DataTableColumn[] /** Include an event filter above the table (EventsNode only) */ showEventFilter?: boolean /** Include a free text search field (PersonsNode only) */ diff --git a/frontend/src/scenes/cohorts/CohortEdit.tsx b/frontend/src/scenes/cohorts/CohortEdit.tsx index 70ad45b833e00..2221675aaef52 100644 --- a/frontend/src/scenes/cohorts/CohortEdit.tsx +++ b/frontend/src/scenes/cohorts/CohortEdit.tsx @@ -199,6 +199,7 @@ export function CohortEdit({ id }: CohortLogicProps): JSX.Element { )} + {/* The typeof here is needed to pass the cohort id to the query below. Using `isNewCohort` won't work */} {typeof cohort.id === 'number' && ( <> diff --git a/frontend/src/scenes/persons/Person.tsx b/frontend/src/scenes/persons/Person.tsx index bc6a82f5e0478..0257211badbe8 100644 --- a/frontend/src/scenes/persons/Person.tsx +++ b/frontend/src/scenes/persons/Person.tsx @@ -163,6 +163,7 @@ export function Person(): JSX.Element | null { =10'} peerDependencies: axe-core: ^3 || ^4 - cypress: ^10 + cypress: ^10 || ^11 dependencies: axe-core: 4.5.1 - cypress: 10.11.0 + cypress: 11.2.0 dev: true - /cypress-terminal-report/4.1.2_cypress@10.11.0: + /cypress-terminal-report/4.1.2_cypress@11.2.0: resolution: {integrity: sha512-UyCR7AJXMJYt87JXBAxdZxWC+FXG1S7+ePVACcumUQZq5lH490pXPPg47KY/7J6yyFatT6da+Mb1/J6E7K8sLg==} peerDependencies: cypress: '>=4.10.0' dependencies: chalk: 4.1.2 - cypress: 10.11.0 + cypress: 11.2.0 fs-extra: 10.1.0 semver: 7.3.8 tv4: 1.3.0 dev: true - /cypress/10.11.0: - resolution: {integrity: sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA==} + /cypress/11.2.0: + resolution: {integrity: sha512-u61UGwtu7lpsNWLUma/FKNOsrjcI6wleNmda/TyKHe0dOBcVjbCPlp1N6uwFZ0doXev7f/91YDpU9bqDCFeBLA==} engines: {node: '>=12.0.0'} hasBin: true requiresBuild: true @@ -11599,54 +11599,54 @@ packages: engines: {node: '>=8'} dev: true - /kea-forms/3.0.3_kea@3.0.4: + /kea-forms/3.0.3_kea@3.1.0: resolution: {integrity: sha512-ApiirM7K103ULa0hNNcJHiJ0ffvuVIn9Nwg4wsEadfyraV9GLrWVbUeZWW0qFI2zTlkizDplM/gc3gGUfQTs9g==} peerDependencies: kea: '>= 3.0.1' dependencies: - kea: 3.0.4_react@16.14.0 + kea: 3.1.0_react@16.14.0 dev: false - /kea-loaders/3.0.0_kea@3.0.4: + /kea-loaders/3.0.0_kea@3.1.0: resolution: {integrity: sha512-YQWXMthnfsxRGSI+pXehgfPWE/hjck9mD2oGlQv86wE6MWo/gGn1lT/gAyF80s9YHOLMOL43Nei5py3K6vwBbg==} peerDependencies: kea: '>= 3' dependencies: - kea: 3.0.4_react@16.14.0 + kea: 3.1.0_react@16.14.0 dev: false - /kea-localstorage/3.1.0_kea@3.0.4: + /kea-localstorage/3.1.0_kea@3.1.0: resolution: {integrity: sha512-OFByJwu88Ro9C9A1bqY5n8v1JYl6ArXX1zC75qpN8HorrReoM9Kf0gZdVspyExsRuX5en9ga64vsk54+jW4Kvw==} peerDependencies: kea: '>= 3' dependencies: - kea: 3.0.4_react@16.14.0 + kea: 3.1.0_react@16.14.0 dev: false - /kea-router/3.1.3_kea@3.0.4: + /kea-router/3.1.3_kea@3.1.0: resolution: {integrity: sha512-+Pk+RZ4PzEXskL3D3903M33Mt94vqJlHb0LCBRn7lFTg0age/53n8CkMvB6QT9SgvtDXqiWgViCWdtaACDQY7A==} peerDependencies: kea: '>= 3' dependencies: - kea: 3.0.4_react@16.14.0 + kea: 3.1.0_react@16.14.0 url-pattern: 1.0.3 dev: false - /kea-subscriptions/3.0.0_kea@3.0.4: + /kea-subscriptions/3.0.0_kea@3.1.0: resolution: {integrity: sha512-26iEvvAV6+s1YtFajjDndP/ht32Rx2IuElr8O6yUsOuMSPLn55/EpRIMEyeFu0Amim7BOVfn9MoKFsSSlr/Ilw==} peerDependencies: kea: '>= 3' dependencies: - kea: 3.0.4_react@16.14.0 + kea: 3.1.0_react@16.14.0 dev: false - /kea-test-utils/0.2.2_kea@3.0.4: + /kea-test-utils/0.2.2_kea@3.1.0: resolution: {integrity: sha512-WiTfn2jd9GLeMJvRIKqj7kIDaKlxk2JgzWB+aKMvcQoyNazHvyLGblE9OzZFMMRgRgs3uGs+/NXuwvPt76m6dg==} peerDependencies: kea: '>= 2.4.10' dependencies: - kea: 3.0.4_react@16.14.0 - kea-waitfor: 0.2.1_kea@3.0.4 + kea: 3.1.0_react@16.14.0 + kea-waitfor: 0.2.1_kea@3.1.0 dev: true /kea-typegen/3.1.4_typescript@4.8.4: @@ -11667,23 +11667,23 @@ packages: - supports-color dev: true - /kea-waitfor/0.2.1_kea@3.0.4: + /kea-waitfor/0.2.1_kea@3.1.0: resolution: {integrity: sha512-oXZ42wZl46+KShuPORgAHKFQr1ewrNJ1ZVBUFLDyn4J3XTndQqCvN0GaFrSCIR0qeBzGHtNu/WwPgVGsmDH8DA==} peerDependencies: kea: '>= 2.2.1' dependencies: - kea: 3.0.4_react@16.14.0 + kea: 3.1.0_react@16.14.0 - /kea-window-values/3.0.0_kea@3.0.4: + /kea-window-values/3.0.0_kea@3.1.0: resolution: {integrity: sha512-sGaUiTgJHS3DY9j3V3G6gXQHYYZioUMWdkfEtV2W4XIPGPobZciZ0tkoPN3ihvcOpbyT8g2FLgvlG0HSscxOWw==} peerDependencies: kea: '>= 3' dependencies: - kea: 3.0.4_react@16.14.0 + kea: 3.1.0_react@16.14.0 dev: false - /kea/3.0.4_react@16.14.0: - resolution: {integrity: sha512-Rf+hJ1MPBqRhrLgqTWHxidpzz2M83GScWmdM6EQhfePp9tQR94E9c3UEh86Wcy+NhGwN763pEZLSIgYibV1jcA==} + /kea/3.1.0_react@16.14.0: + resolution: {integrity: sha512-RPuhKUH9PsIikRlP3ppfcy+xogoVZWv/3v2HPd8nyYlHEbuKntmRJRMIBNTvSk2hlKBIRbpPG/UYW7cRvhEeng==} peerDependencies: react: '>= 16.8' dependencies: