Skip to content

Commit

Permalink
Merge branch 'master' into feat/annotations-in-data-management
Browse files Browse the repository at this point in the history
# Conflicts:
#	frontend/__snapshots__/scenes-app-notebooks--recordings-playlist.png
#	frontend/src/scenes/persons/Persons.tsx
  • Loading branch information
benjackwhite committed Nov 7, 2023
2 parents f4736d8 + 61a563a commit a7eeada
Show file tree
Hide file tree
Showing 74 changed files with 501 additions and 221 deletions.
12 changes: 6 additions & 6 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ module.exports = {
},
],
'react/forbid-dom-props': [
1,
'warn',
{
forbid: [
{
Expand All @@ -98,7 +98,7 @@ module.exports = {
},
],
'posthog/warn-elements': [
1,
'warn',
{
forbid: [
{
Expand Down Expand Up @@ -146,7 +146,7 @@ module.exports = {
},
],
'react/forbid-elements': [
2,
'error',
{
forbid: [
{
Expand Down Expand Up @@ -200,9 +200,9 @@ module.exports = {
],
},
],
'no-constant-condition': 0,
'no-prototype-builtins': 0,
'no-irregular-whitespace': 0,
'no-constant-condition': 'off',
'no-prototype-builtins': 'off',
'no-irregular-whitespace': 'off',
},
overrides: [
{
Expand Down
4 changes: 3 additions & 1 deletion bin/docker-server-unit
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ trap 'rm -rf "$PROMETHEUS_MULTIPROC_DIR"' EXIT
export PROMETHEUS_METRICS_EXPORT_PORT=8001
export STATSD_PORT=${STATSD_PORT:-8125}

exec /usr/local/bin/docker-entrypoint.sh unitd --no-daemon
# We need to run as --user root so that nginx unit can proxy the control socket for stats
# However each application is run as "nobody"
exec /usr/local/bin/docker-entrypoint.sh unitd --no-daemon --user root
78 changes: 78 additions & 0 deletions bin/unit_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import http.client
import json
from prometheus_client import CollectorRegistry, Gauge, multiprocess, generate_latest

UNIT_CONNECTIONS_ACCEPTED_TOTAL = Gauge(
"unit_connections_accepted_total",
"",
multiprocess_mode="livesum",
)
UNIT_CONNECTIONS_ACTIVE = Gauge(
"unit_connections_active",
"",
multiprocess_mode="livesum",
)
UNIT_CONNECTIONS_CLOSED = Gauge(
"unit_connections_closed",
"",
multiprocess_mode="livesum",
)
UNIT_CONNECTIONS_IDLE = Gauge(
"unit_connections_idle",
"",
multiprocess_mode="livesum",
)
UNIT_CONNECTIONS_TOTAL = Gauge(
"unit_requests_total",
"",
multiprocess_mode="livesum",
)
UNIT_PROCESSES_RUNNING_GAUGE = Gauge(
"unit_application_processes_running", "", multiprocess_mode="livesum", labelnames=["application"]
)
UNIT_PROCESSES_STARTING_GAUGE = Gauge(
"unit_application_processes_starting", "", multiprocess_mode="livesum", labelnames=["application"]
)
UNIT_PROCESSES_IDLE_GAUGE = Gauge(
"unit_application_processes_idle", "", multiprocess_mode="livesum", labelnames=["application"]
)
UNIT_REQUESTS_ACTIVE_GAUGE = Gauge(
"unit_application_requests_active", "", multiprocess_mode="livesum", labelnames=["application"]
)


def application(environ, start_response):
connection = http.client.HTTPConnection("localhost:8081")
connection.request("GET", "/status")
response = connection.getresponse()

statj = json.loads(response.read())
connection.close()

UNIT_CONNECTIONS_ACCEPTED_TOTAL.set(statj["connections"]["accepted"])
UNIT_CONNECTIONS_ACTIVE.set(statj["connections"]["active"])
UNIT_CONNECTIONS_IDLE.set(statj["connections"]["idle"])
UNIT_CONNECTIONS_CLOSED.set(statj["connections"]["closed"])
UNIT_CONNECTIONS_TOTAL.set(statj["requests"]["total"])

for application in statj["applications"].keys():
UNIT_PROCESSES_RUNNING_GAUGE.labels(application=application).set(
statj["applications"][application]["processes"]["running"]
)
UNIT_PROCESSES_STARTING_GAUGE.labels(application=application).set(
statj["applications"][application]["processes"]["starting"]
)
UNIT_PROCESSES_IDLE_GAUGE.labels(application=application).set(
statj["applications"][application]["processes"]["idle"]
)
UNIT_REQUESTS_ACTIVE_GAUGE.labels(application=application).set(
statj["applications"][application]["requests"]["active"]
)

start_response("200 OK", [("Content-Type", "text/plain")])
# Create the prometheus multi-process metric registry here
# This will aggregate metrics we send from the Django app
# We prepend our unit metrics here.
registry = CollectorRegistry()
multiprocess.MultiProcessCollector(registry)
yield generate_latest(registry)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-other-billing-v2--billing-v-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
144 changes: 144 additions & 0 deletions frontend/src/lib/components/IntervalFilter/intervalFilterLogic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { kea, props, key, path, connect, actions, reducers, listeners } from 'kea'
import { objectsEqual, dateMapping } from 'lib/utils'
import type { intervalFilterLogicType } from './intervalFilterLogicType'
import { IntervalKeyType, Intervals, intervals } from 'lib/components/IntervalFilter/intervals'
import { BaseMathType, InsightLogicProps, IntervalType } from '~/types'
import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils'
import { dayjs } from 'lib/dayjs'
import { InsightQueryNode, TrendsQuery } from '~/queries/schema'
import { lemonToast } from 'lib/lemon-ui/lemonToast'
import { BASE_MATH_DEFINITIONS } from 'scenes/trends/mathsLogic'
import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic'

export const intervalFilterLogic = kea<intervalFilterLogicType>([
props({} as InsightLogicProps),
key(keyForInsightLogicProps('new')),
path((key) => ['lib', 'components', 'IntervalFilter', 'intervalFilterLogic', key]),
connect((props: InsightLogicProps) => ({
actions: [insightVizDataLogic(props), ['updateQuerySource']],
values: [insightVizDataLogic(props), ['interval', 'querySource']],
})),
actions(() => ({
setInterval: (interval: IntervalKeyType) => ({ interval }),
setEnabledIntervals: (enabledIntervals: Intervals) => ({ enabledIntervals }),
})),
reducers(() => ({
enabledIntervals: [
{ ...intervals } as Intervals,
{
setEnabledIntervals: (_, { enabledIntervals }) => enabledIntervals,
},
],
})),
listeners(({ values, actions, selectors }) => ({
setInterval: ({ interval }) => {
if (values.interval !== interval) {
actions.updateQuerySource({ interval } as Partial<InsightQueryNode>)
}
},
updateQuerySource: ({ querySource }, _, __, previousState) => {
const { date_from, date_to } = querySource.dateRange || {}
const previousDateRange = selectors.querySource(previousState)?.dateRange || {}

let activeUsersMath: BaseMathType.WeeklyActiveUsers | BaseMathType.MonthlyActiveUsers | null = null

// We disallow grouping by certain intervals for weekly active users and monthly active users views
// e.g. WAUs grouped by month. Here, look for the first event/action running WAUs/MAUs math and
// pass that down to the interval filter to determine what groupings are allowed.
for (const series of (values.querySource as TrendsQuery)?.series || []) {
if (series.math === BaseMathType.WeeklyActiveUsers) {
activeUsersMath = BaseMathType.WeeklyActiveUsers
break
}

if (series.math === BaseMathType.MonthlyActiveUsers) {
activeUsersMath = BaseMathType.MonthlyActiveUsers
break
}
}

const enabledIntervals: Intervals = { ...intervals }

if (activeUsersMath) {
// Disallow grouping by hour for WAUs/MAUs as it's an expensive query that produces a view that's not useful for users
enabledIntervals.hour = {
...enabledIntervals.hour,
disabledReason:
'Grouping by hour is not supported on insights with weekly or monthly active users series.',
}

// Disallow grouping by month for WAUs as the resulting view is misleading to users
if (activeUsersMath === BaseMathType.WeeklyActiveUsers) {
enabledIntervals.month = {
...enabledIntervals.month,
disabledReason:
'Grouping by month is not supported on insights with weekly active users series.',
}
}
}

actions.setEnabledIntervals(enabledIntervals)

// If the user just flipped an event action to use WAUs/MAUs math and their
// current interval is unsupported by the math type, switch their interval
// to an appropriate allowed interval and inform them of the change via a toast
if (
activeUsersMath &&
(values.querySource as TrendsQuery)?.interval &&
enabledIntervals[(values.querySource as TrendsQuery).interval as IntervalType].disabledReason
) {
if (values.interval === 'hour') {
lemonToast.info(
`Switched to grouping by day, because "${BASE_MATH_DEFINITIONS[activeUsersMath].name}" does not support grouping by ${values.interval}.`
)
actions.updateQuerySource({ interval: 'day' } as Partial<InsightQueryNode>)
} else {
lemonToast.info(
`Switched to grouping by week, because "${BASE_MATH_DEFINITIONS[activeUsersMath].name}" does not support grouping by ${values.interval}.`
)
actions.updateQuerySource({ interval: 'week' } as Partial<InsightQueryNode>)
}
return
}

if (
!date_from ||
(objectsEqual(date_from, previousDateRange.date_from) &&
objectsEqual(date_to, previousDateRange.date_to))
) {
return
}

// automatically set an interval for fixed date ranges
if (
date_from &&
date_to &&
dayjs(querySource.dateRange?.date_from).isValid() &&
dayjs(querySource.dateRange?.date_to).isValid()
) {
if (dayjs(date_to).diff(dayjs(date_from), 'day') <= 3) {
actions.updateQuerySource({ interval: 'hour' } as Partial<InsightQueryNode>)
} else if (dayjs(date_to).diff(dayjs(date_from), 'month') <= 3) {
actions.updateQuerySource({ interval: 'day' } as Partial<InsightQueryNode>)
} else {
actions.updateQuerySource({ interval: 'month' } as Partial<InsightQueryNode>)
}
return
}
// get a defaultInterval for dateOptions that have a default value
let interval: IntervalType = 'day'
for (const { key, values, defaultInterval } of dateMapping) {
if (
values[0] === date_from &&
values[1] === (date_to || undefined) &&
key !== 'Custom' &&
defaultInterval
) {
interval = defaultInterval
break
}
}
actions.updateQuerySource({ interval } as Partial<InsightQueryNode>)
},
})),
])
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const SortableProperty = ({
className={clsx(sortable ? 'cursor-move' : 'cursor-auto')}
{...attributes}
{...listeners}
// eslint-disable-next-line react/forbid-dom-props
style={{
transform: CSS.Translate.toString(transform),
transition,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export function ComparingPropertyFilters(): JSX.Element {
propertyFilters={[...propertyFilters]}
onChange={() => {}}
pageKey={'pageKey'}
style={{ marginBottom: 0 }}
showNestedArrow
eventNames={[]}
/>
Expand All @@ -48,7 +47,6 @@ export function ComparingPropertyFilters(): JSX.Element {
propertyFilters={[...propertyFilters]}
onChange={() => {}}
pageKey={'pageKey'}
style={{ marginBottom: 0 }}
eventNames={[]}
disablePopover={true}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { CSSProperties, useEffect } from 'react'
import React, { useEffect } from 'react'
import { useValues, BindLogic, useActions } from 'kea'
import { propertyFilterLogic } from './propertyFilterLogic'
import { FilterRow } from './components/FilterRow'
Expand All @@ -15,7 +15,6 @@ interface PropertyFiltersProps {
pageKey: string
showConditionBadge?: boolean
disablePopover?: boolean
style?: CSSProperties
taxonomicGroupTypes?: TaxonomicFilterGroupType[]
hogQLTable?: string
showNestedArrow?: boolean
Expand All @@ -39,7 +38,6 @@ export function PropertyFilters({
disablePopover = false, // use bare PropertyFilter without popover
taxonomicGroupTypes,
hogQLTable,
style = {},
showNestedArrow = false,
eventNames = [],
orFiltering = false,
Expand All @@ -62,7 +60,7 @@ export function PropertyFilters({
}, [propertyFilters])

return (
<div className="PropertyFilters" style={style}>
<div className="PropertyFilters">
{showNestedArrow && !disablePopover && <div className="PropertyFilters__prefix">{<>&#8627;</>}</div>}
<div className="PropertyFilters__content">
<BindLogic logic={propertyFilterLogic} props={logicProps}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export function OperatorValueSelect({
/>
</div>
{!isOperatorFlag(currentOperator || PropertyOperator.Exact) && type && propkey && (
<div className="flex-1" style={{ minWidth: '10rem' }} data-attr="taxonomic-value-select">
<div className="flex-1 min-w-40" data-attr="taxonomic-value-select">
<PropertyValue
type={type}
key={propkey}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { CSSProperties } from 'react'
import { AnyPropertyFilter } from '~/types'
import { PropertyFilterButton } from './PropertyFilterButton'

type Props = {
filters: AnyPropertyFilter[]
style?: CSSProperties
}

const PropertyFiltersDisplay: React.FunctionComponent<Props> = ({ filters, style }: Props) => {
const PropertyFiltersDisplay = ({ filters }: { filters: AnyPropertyFilter[] }): JSX.Element => {
return (
<div className="PropertyFilters mb-4" style={style}>
<div className="PropertyFilters flex-wrap">
{filters &&
filters.map((item) => {
return <PropertyFilterButton key={item.key} item={item} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ export function PropertyGroupFilters({
? (group.values as AnyPropertyFilter[])
: null
}
style={{ marginBottom: 0 }}
onChange={(properties) => {
setPropertyFilters(properties, propertyGroupIndex)
}}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/components/SeriesGlyph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface SeriesGlyphProps {

export function SeriesGlyph({ className, style, children, variant }: SeriesGlyphProps): JSX.Element {
return (
// eslint-disable-next-line react/forbid-dom-props
<div className={`graph-series-glyph ${variant || ''} ${className}`} style={style}>
{children}
</div>
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/lib/components/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function createdAtColumn<T extends Record<string, any> = Record<string, a
render: function RenderCreatedAt(_, item): JSX.Element | undefined | '' {
return (
item.created_at && (
<div style={{ whiteSpace: 'nowrap' }}>
<div className="whitespace-nowrap">
<TZLabel time={item.created_at} />
</div>
)
Expand All @@ -33,6 +33,7 @@ export function createdByColumn<T extends Record<string, any> = Record<string, a
{item.created_by && (
<ProfilePicture name={item.created_by.first_name} email={item.created_by.email} size="md" />
)}
{/* eslint-disable-next-line react/forbid-dom-props */}
<div style={{ maxWidth: 250, width: 'auto', verticalAlign: 'middle', marginLeft: 8 }}>
{item.created_by ? item.created_by.first_name || item.created_by.email : '-'}
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/components/Table/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useWindowSize } from 'lib/hooks/useWindowSize'
import { getBreakpoint } from 'lib/utils/responsiveUtils'

export function normalizeColumnTitle(title: string | JSX.Element): JSX.Element {
return <span style={{ whiteSpace: 'nowrap' }}>{title}</span>
return <span className="whitespace-nowrap">{title}</span>
}

// Returns a boolean indicating whether table should be scrolling or not given a specific
Expand Down
Loading

0 comments on commit a7eeada

Please sign in to comment.