Skip to content

Commit

Permalink
Merge branch 'master' into feat/hackathon-3000-feed-properties
Browse files Browse the repository at this point in the history
# Conflicts:
#	frontend/src/scenes/notebooks/Notebook/Editor.tsx
#	frontend/src/scenes/notebooks/Notebook/utils.ts
#	frontend/src/scenes/notebooks/NotebooksTable/ContainsTypeFilter.tsx
#	frontend/src/scenes/persons/PersonFeedCanvas.tsx
#	frontend/src/types.ts
  • Loading branch information
benjackwhite committed Oct 26, 2023
2 parents 1c95e71 + 8a188cc commit ed651c4
Show file tree
Hide file tree
Showing 20 changed files with 1,066 additions and 478 deletions.
12 changes: 12 additions & 0 deletions docker-compose.base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@ services:
command: ./bin/start-backend & ./bin/start-frontend
restart: on-failure

capture:
image: ghcr.io/posthog/capture:main
restart: on-failure
environment:
ADDRESS: '0.0.0.0:3000'
KAFKA_TOPIC: 'events_plugin_ingestion'
KAFKA_HOSTS: 'kafka:9092'
REDIS_URL: 'redis://redis:6379/'
depends_on:
- redis
- kafka

plugins:
command: ./bin/plugin-server --no-restart-loop
restart: on-failure
Expand Down
9 changes: 9 additions & 0 deletions docker-compose.dev-full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ services:
environment:
- DEBUG=1

capture:
extends:
file: docker-compose.base.yml
service: capture
ports:
- 3000:3000
environment:
- DEBUG=1

plugins:
extends:
file: docker-compose.base.yml
Expand Down
11 changes: 11 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ services:
- '1080:1080'
- '1025:1025'

# Optional capture
capture:
profiles: ['capture-rs']
extends:
file: docker-compose.base.yml
service: capture
ports:
- 3000:3000
environment:
- DEBUG=1

# Temporal containers
elasticsearch:
extends:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function LemonCalendarRangeInline({
// How many months fit on the screen, capped between 1..2
function getMonthCount(): number {
const width =
// eslint-disable-next-line valid-typeof
typeof window === undefined ? WIDTH_OF_ONE_CALENDAR_MONTH * CALENDARS_IF_NO_WINDOW : window.innerWidth
return Math.min(Math.max(1, Math.floor(width / WIDTH_OF_ONE_CALENDAR_MONTH)), 2)
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/lib/lemon-ui/LemonMenu/LemonMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function LemonMenu({
)

const _onVisibilityChange = useCallback(
(visible) => {
(visible: boolean) => {
onVisibilityChange?.(visible)
if (visible && activeItemIndex && activeItemIndex > -1) {
// Scroll the active item into view once the menu is open (i.e. in the next tick)
Expand Down Expand Up @@ -256,7 +256,7 @@ const LemonMenuItemButton: FunctionComponent<LemonMenuItemButtonProps & React.Re
size={size}
{...buttonProps}
>
{label}
{label as string | JSX.Element}
{keyboardShortcut && (
<div className="-mr-0.5 inline-flex grow justify-end">
{/* Show the keyboard shortcut on the right */}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/lemon-ui/LemonSelect/LemonSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export interface LemonSelectPropsNonClearable<T> extends LemonSelectPropsBase<T>

export type LemonSelectProps<T> = LemonSelectPropsClearable<T> | LemonSelectPropsNonClearable<T>

export function LemonSelect<T>({
export function LemonSelect<T extends string | number | boolean | null>({
value = null,
onChange,
onSelect,
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/lib/lemon-ui/LemonTable/TableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ function TableRowRaw<T extends Record<string, any>>({
// of a class indicating that scrollability to `table` caused the component to lag due to unneded rerendering of rows.
export const TableRow = React.memo(TableRowRaw) as typeof TableRowRaw

function isTableCellRepresentation(contents: React.ReactNode): contents is TableCellRepresentation {
function isTableCellRepresentation(
contents: React.ReactNode | TableCellRepresentation
): contents is TableCellRepresentation {
return !!contents && typeof contents === 'object' && !React.isValidElement(contents)
}
2 changes: 1 addition & 1 deletion frontend/src/lib/lemon-ui/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useLayoutEffect, useRef, useState } from 'react'
* @private
*/
export function useSliderPositioning<C extends HTMLElement, S extends HTMLElement>(
currentValue: string | number | null | undefined,
currentValue: React.Key | null | undefined,
transitionMs: number
): {
containerRef: React.RefObject<C>
Expand Down
14 changes: 8 additions & 6 deletions frontend/src/scenes/billing/BillingLimitInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ export const BillingLimitInput = ({ product }: { product: BillingProductV2Type }
if (value === undefined) {
return actuallyUpdateLimit()
}
const productAndAddonTiers: BillingV2TierType[][] = [
product.tiers,
...product.addons
?.filter((addon: BillingProductV2AddonType) => addon.subscribed)
?.map((addon: BillingProductV2AddonType) => addon.tiers),
].filter(Boolean) as BillingV2TierType[][]

const addonTiers = product.addons
?.filter((addon: BillingProductV2AddonType) => addon.subscribed)
?.map((addon: BillingProductV2AddonType) => addon.tiers)

const productAndAddonTiers: BillingV2TierType[][] = [product.tiers, ...addonTiers].filter(
Boolean
) as BillingV2TierType[][]

const newAmountAsUsage = product.tiers
? convertAmountToUsage(`${value}`, productAndAddonTiers, billing?.discount_percent)
Expand Down
12 changes: 6 additions & 6 deletions frontend/src/scenes/billing/billingProductLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,12 @@ export const billingProductLogic = kea<billingProductLogicType>([
(billing, product, isEditingBillingLimit, billingLimitInput, customLimitUsd) => {
// cast the product as a product, not an addon, to avoid TS errors. This is fine since we're just getting the tiers.
product = product as BillingProductV2Type
const productAndAddonTiers: BillingV2TierType[][] = [
product.tiers,
...product.addons
?.filter((addon: BillingProductV2AddonType) => addon.subscribed)
?.map((addon: BillingProductV2AddonType) => addon.tiers),
].filter(Boolean) as BillingV2TierType[][]
const addonTiers = product.addons
?.filter((addon: BillingProductV2AddonType) => addon.subscribed)
?.map((addon: BillingProductV2AddonType) => addon.tiers)
const productAndAddonTiers: BillingV2TierType[][] = [product.tiers, ...addonTiers].filter(
Boolean
) as BillingV2TierType[][]
return product.tiers
? isEditingBillingLimit
? convertAmountToUsage(`${billingLimitInput}`, productAndAddonTiers, billing?.discount_percent)
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/scenes/experiments/experimentLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,7 @@ export const experimentLogic = kea<experimentLogicType>([
let index = -1
if (insightType === InsightType.FUNNELS) {
// Funnel Insight is displayed in order of decreasing count
index = ([...experimentResults?.insight] as FunnelStep[][])
index = ([...experimentResults.insight] as FunnelStep[][])
.sort((a, b) => b[0]?.count - a[0]?.count)
.findIndex(
(variantFunnel: FunnelStep[]) => variantFunnel[0]?.breakdown_value?.[0] === variant
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/scenes/feature-flags/featureFlagLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,10 @@ export const featureFlagLogic = kea<featureFlagLogicType>([
if (!state) {
return state
}
const groups = [...state?.filters.groups, { properties: [], rollout_percentage: 0, variant: null }]
const groups = [
...(state?.filters?.groups || []),
{ properties: [], rollout_percentage: 0, variant: null },
]
return { ...state, filters: { ...state.filters, groups } }
},
addRollbackCondition: (state) => {
Expand All @@ -291,7 +294,7 @@ export const featureFlagLogic = kea<featureFlagLogicType>([
return state
}

const groups = [...state?.filters.groups]
const groups = [...(state?.filters?.groups || [])]
if (newRolloutPercentage !== undefined) {
groups[index] = { ...groups[index], rollout_percentage: newRolloutPercentage }
}
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/scenes/notebooks/Notebook/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { FloatingMenu } from '@tiptap/extension-floating-menu'
import StarterKit from '@tiptap/starter-kit'
import ExtensionPlaceholder from '@tiptap/extension-placeholder'
import ExtensionDocument from '@tiptap/extension-document'
import TaskItem from '@tiptap/extension-task-item'
import TaskList from '@tiptap/extension-task-list'

import { NotebookNodeFlagCodeExample } from '../Nodes/NotebookNodeFlagCodeExample'
import { NotebookNodeFlag } from '../Nodes/NotebookNodeFlag'
Expand Down Expand Up @@ -96,6 +98,10 @@ export function Editor(): JSX.Element {
}
},
}),
TaskList,
TaskItem.configure({
nested: true,
}),
NotebookMarkLink,
NotebookNodeBacklink,
NotebookNodeQuery,
Expand Down
34 changes: 31 additions & 3 deletions frontend/src/scenes/notebooks/Notebook/Notebook.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,40 @@
height: 0;
}

> ul {
> ol {
list-style-type: decimal;
}

ul {
list-style-type: disc;
}

> ol {
list-style-type: decimal;
> ul[data-type='taskList'] {
list-style-type: none;
padding-left: 0px;

li {
display: flex;

> label {
flex: 0 0 auto;
margin-right: 0.5rem;
user-select: none;
}

> div {
flex: 1 1 auto;
}

ul li,
ol li {
display: list-item;
}

ul[data-type='taskList'] > li {
display: flex;
}
}
}

> ul,
Expand Down
19 changes: 11 additions & 8 deletions frontend/src/scenes/persons/RelatedFeatureFlags.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ export function RelatedFeatureFlags({ distinctId, groups }: Props): JSX.Element
},
},
]

const options = [
{ label: 'All types', value: 'all' },
{
label: FeatureFlagReleaseType.ReleaseToggle,
value: FeatureFlagReleaseType.ReleaseToggle,
},
{ label: FeatureFlagReleaseType.Variants, value: FeatureFlagReleaseType.Variants },
]

return (
<>
<div className="flex justify-between mb-4">
Expand All @@ -131,14 +141,7 @@ export function RelatedFeatureFlags({ distinctId, groups }: Props): JSX.Element
<b>Type</b>
</span>
<LemonSelect
options={[
{ label: 'All types', value: 'all' },
{
label: FeatureFlagReleaseType.ReleaseToggle,
value: FeatureFlagReleaseType.ReleaseToggle,
},
{ label: FeatureFlagReleaseType.Variants, value: FeatureFlagReleaseType.Variants },
]}
options={options}
onChange={(type) => {
if (type) {
if (type === 'all') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ export function SessionRecordingsFilters({
size="small"
onClick={() => setShowAdvancedFilters(!showAdvancedFilters)}
disabledReason={
hasAdvancedFilters &&
'You are only allowed person filters and a single pageview event to switch back to simple filters'
hasAdvancedFilters
? 'You are only allowed person filters and a single pageview event (filtered by current url) to switch back to simple filters'
: undefined
}
data-attr={`session-recordings-show-${showAdvancedFilters ? 'simple' : 'advanced'}-filters`}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,30 +79,64 @@ export const getDefaultFilters = (personUUID?: PersonUUID): RecordingFilters =>
return personUUID ? DEFAULT_PERSON_RECORDING_FILTERS : DEFAULT_RECORDING_FILTERS
}

function isPageViewFilter(filter: Record<string, any>): boolean {
return filter.name === '$pageview'
}
function isCurrentURLPageViewFilter(eventsFilter: Record<string, any>): boolean {
const hasSingleProperty = Array.isArray(eventsFilter.properties) && eventsFilter.properties?.length === 1
const isCurrentURLProperty = hasSingleProperty && eventsFilter.properties[0].key === '$current_url'
return isPageViewFilter(eventsFilter) && isCurrentURLProperty
}

// checks are stored against filter keys so that the type system enforces adding a check when we add new filters
const advancedFilterChecks: Record<
keyof RecordingFilters,
(filters: RecordingFilters, defaultFilters: RecordingFilters) => boolean
> = {
actions: (filters) => (filters.actions ? filters.actions.length > 0 : false),
events: function (filters: RecordingFilters): boolean {
const eventsFilters = filters.events || []
// simple filters allow a single $pageview event filter with $current_url as the selected property
// anything else is advanced
return (
eventsFilters.length > 1 ||
(!!eventsFilters[0] &&
(!isPageViewFilter(eventsFilters[0]) || !isCurrentURLPageViewFilter(eventsFilters[0])))
)
},
properties: function (): boolean {
// TODO is this right? should we ever care about properties for choosing between advanced and simple?
return false
},
date_from: (filters, defaultFilters) => filters.date_from != defaultFilters.date_from,
date_to: (filters, defaultFilters) => filters.date_to != defaultFilters.date_to,
session_recording_duration: (filters, defaultFilters) =>
!equal(filters.session_recording_duration, defaultFilters.session_recording_duration),
duration_type_filter: (filters, defaultFilters) =>
filters.duration_type_filter !== defaultFilters.duration_type_filter,
console_search_query: (filters) =>
filters.console_search_query ? filters.console_search_query.trim().length > 0 : false,
console_logs: (filters) => (filters.console_logs ? filters.console_logs.length > 0 : false),
filter_test_accounts: (filters) => filters.filter_test_accounts ?? false,
}

export const addedAdvancedFilters = (
filters: RecordingFilters | undefined,
defaultFilters: RecordingFilters
): boolean => {
if (!filters) {
// if there are no filters or if some filters are not present then the page is still booting up
if (!filters || filters.session_recording_duration === undefined || filters.date_from === undefined) {
return false
}

const hasActions = filters.actions ? filters.actions.length > 0 : false
const hasChangedDateFrom = filters.date_from != defaultFilters.date_from
const hasChangedDateTo = filters.date_to != defaultFilters.date_to
const hasConsoleLogsFilters = filters.console_logs ? filters.console_logs.length > 0 : false
const hasChangedDuration = !equal(filters.session_recording_duration, defaultFilters.session_recording_duration)
const eventsFilters = filters.events || []
const hasAdvancedEvents = eventsFilters.length > 1 || (!!eventsFilters[0] && eventsFilters[0].name != '$pageview')

return (
hasActions ||
hasAdvancedEvents ||
hasChangedDuration ||
hasChangedDateFrom ||
hasChangedDateTo ||
hasConsoleLogsFilters
)
// keeps results with the keys for printing when debugging
const checkResults = Object.keys(advancedFilterChecks).map((key) => ({
key,
result: advancedFilterChecks[key](filters, defaultFilters),
}))

// if any check is true, then this is an advanced filter
return checkResults.some((checkResult) => checkResult.result)
}

export const defaultPageviewPropertyEntityFilter = (
Expand Down Expand Up @@ -348,8 +382,14 @@ export const sessionRecordingsPlaylistLogic = kea<sessionRecordingsPlaylistLogic
showAdvancedFilters: [
addedAdvancedFilters(props.filters, getDefaultFilters(props.personUUID)),
{
setFilters: (showingAdvancedFilters, { filters }) =>
addedAdvancedFilters(filters, getDefaultFilters(props.personUUID)) ? true : showingAdvancedFilters,
persist: true,
},
{
setFilters: (showingAdvancedFilters, { filters }) => {
return addedAdvancedFilters(filters, getDefaultFilters(props.personUUID))
? true
: showingAdvancedFilters
},
setShowAdvancedFilters: (_, { showAdvancedFilters }) => showAdvancedFilters,
},
],
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/scenes/trends/trendsDataLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export const trendsDataLogic = kea<trendsDataLogicType>([

actions.setInsightData({
...values.insightData,
result: [...values.insightData?.result, ...(response.result ? response.result : [])],
result: [...values.insightData.result, ...(response.result ? response.result : [])],
next: response.next,
})

Expand Down
Loading

0 comments on commit ed651c4

Please sign in to comment.