From 87f8e53a9e612b95c6d1e9f9477fc550af81826b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Oberm=C3=BCller?= Date: Mon, 10 Jun 2024 15:00:19 +0200 Subject: [PATCH 01/35] fix(insights): fix insight creation with global test account filter flag on (#22840) * fix(insights): fix insight creation with global test account filter flag on * Update UI snapshots for `chromium` (2) * Update UI snapshots for `chromium` (1) * Update UI snapshots for `chromium` (1) --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- ...s-app-insights--trends-line-edit--dark.png | Bin 146505 -> 134275 bytes .../src/queries/nodes/InsightViz/utils.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/__snapshots__/scenes-app-insights--trends-line-edit--dark.png b/frontend/__snapshots__/scenes-app-insights--trends-line-edit--dark.png index e1abf1e81d467c0dd78270f0b2ee7d73b8d442c8..79fe5ec97629d3f15b28ab26964cb8eef20be389 100644 GIT binary patch delta 101041 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%0DM; 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%0A)Ff|c4ITzmMf(|dIt_DmaL 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

k!PLbwQ7@E4uBotV zd+wi3mBGBebR(cA-OvsP+{!3i{MV{Q?l*8|S|UE1MM#5Qr47cFT+nevADRF=E@!B& zKfU$LolsO%lwGT=b%$CoOh^vESVG$6j3Jjfd|>(-w z<+iyPV%$9e-aN z8F08Nf~jl%{0&8oS4i{9)AAZZG+;-Hcc?dWc*o%;_I>#o7z60Y&BbM6akgcDv)12g ztkO7fc?^P`RNK!DSZ3SxWEJPViPzX_5TrL>>?|StIGUPT5Q|=eatj$G@sqVszn}$j zbfV{e(1$zeiaG7!hX?3HoRad=r`4X+kg1lRi^w>INBU7MT%6S}Xa@X^reByJ{)&g& zyP63uWo$a_2YC7XL@yzUdvAH@jv6#|g{6{*3pZ%OEN?31*q zEIrLbKG0q>()XPs_WbYV0$ax3=C?$QSAz;JJoj-w8lN}%{FL7-JD=^84nAjCTT6dk z^K?I9Dyx^nedW<~>oqA207qA7B}SQP%pWuTh)zZ*y{ase8i<+AkdJ@foRBWJ!F^$3 zVyBk|X(NnDUhYtrf6>lLXNzw#(C_)p2Du?U$c8ziov{`N zY_N7nMVvX)X!v+>a^X8@M)5;bsHdlg{n1~RD3Ig~c<2xUh|)>%Q#b(P0K^*3lYH?z z2q~L(bMEs7@Ls2WG$}%PE}((Nqc@w4MLBEYhkrOslZ#N@fmE$jUIo;{Ih{sNLI)x8 zu>>tQ<|o&XkEXEPmpZRsdi9DJ_X!{8JJ8a2`t9BcXd#oGoqhS1>dWvnEBxKX;}OXh zYp2*y^+4bXjIY=dutN(~u^&8HSr{{EA>}>YV@I3I@E;DOJgG|CjD5esS zvZlV6xOeVyV@O6I0WTfh8OTrG%ibdj>Fi`F@}Rd81AW}c-$khf)PN#$c5|~4sBQJ{ zF>Y7LH*%1iD1GbGhJ?Kl{%` z=&cK`A_aw%vnt=|bJJLSp_3(2@W2H`VZDCZt~U6p^{g4>zTx`vD%8MJ>U5UV!G6>j z7t#=;s6-YXx@F*RoM06QFmYcZ>WQV8{H4M25J&mKD&vFjG`X(dU6Ffe^wi4l`k8kZ zK=}9JM2)YFgI#`XSRyI2rMbc~r_6f8wFz|m3t=BODRwnU$ulmSzp?P8mZsnRp6c#t zkB+GDc*fq~&JyZRE%j6gjYt>p-}g{NN?&N(x_GRmM5O;DT!`c*9~sp>VrLs-$>B1S*uu?b$GW#J={Bp9dyUE zS+M)Q`ShhyCoA{T6$0yDLBwTj%3f+2dYwIZvdtRId+#DXgs8?Ge5hqbdg`2;Yjm`?No&@ZDHr%#c71D$VXOHHwkyk% z&g&Xw)PhcWx$0k3a)OuFjUg8!YwG|oFU?9H@$-vQtFsbw5iG2l74LbBt;PtdL7Wu& z+;p&T6f*67KmE&kKyp#J%@RdVPp?*J#uh|*XM79#FJiSlp!|M;lH_sLhda(UC5XVe zIV)fvouy%opHNiP>v0L3)z@BLYGoFuJ4AQ4ghRTpiQlIw2fgWPUI6fD!2i|6dd>+N zKtKn?rg@(AS`WyOAIFDOt6+YqpE8~JvkgOW>H?W@>eCcFSe!t(to6g4MU$RX2`Sf4Wyms+CO33C9JM){)y8qbmoS?YI< zA;->LsQqVN(@`)x-9vC7f6@J3!Y61raQG|utc~GJyp%sEgdgDG5O_$5-B-G6!6+mY zM@2l71fTLbE;$>i$o{$EkNKM=Q8Kxvo66X?1=+Oq?d==D{l>P*v6=dfrStwddR&sy z{xpHZdT0*wTx_Cs%71lygT(+rKdi;-SYeNF#TS2zflf|e{x`bNe+2r={a>AGCmSMT zi+}2q#NEFhn=b6!{H0+XqO(#V7Gi(LUwQFc%B`Hh`Ir2T{Od0LPhA=Lmp$1(b$dDFUp27*-0fG8 zf6;sWCvSfO^hf^LQ~xDrBLBU&$N3-o|L--x|FQr7*6H^@_WwWY|9`6i{{MabfAoLq b|FE-AZlCcsM(crph$Hn#PAvO@?(6>r#$P4t delta 113301 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 z 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 z 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 diff --git a/frontend/src/queries/nodes/InsightViz/utils.ts b/frontend/src/queries/nodes/InsightViz/utils.ts index 06d27e0801fd72..77c671d0abcf1a 100644 --- a/frontend/src/queries/nodes/InsightViz/utils.ts +++ b/frontend/src/queries/nodes/InsightViz/utils.ts @@ -167,7 +167,7 @@ export function getQueryBasedInsightModel(insight: let query if (insight.query) { query = insight.query - } else if (insight.filters && Object.keys(insight.filters).length > 0) { + } else if (insight.filters && Object.keys(insight.filters).filter((k) => k != 'filter_test_accounts').length > 0) { query = { kind: NodeKind.InsightVizNode, source: filtersToQueryNode(insight.filters) } as InsightVizNode } else { query = null From 394ac6f6d9715db304a1a5bfe7de3357226e913b Mon Sep 17 00:00:00 2001 From: David Newell Date: Mon, 10 Jun 2024 14:09:19 +0100 Subject: [PATCH 02/35] feat: Replay Universal Filters (#22612) --- ...ilters-universalfilters--default--dark.png | Bin 0 -> 7286 bytes ...lters-universalfilters--default--light.png | Bin 0 -> 7318 bytes ...-app-insights--trends-line-edit--light.png | Bin 133906 -> 145774 bytes frontend/src/lib/constants.tsx | 1 + .../AndOrFilterSelect.tsx | 5 +- .../filters/RecordingsUniversalFilters.tsx | 126 ++++++++++++++++++ .../playlist/SessionRecordingsPlaylist.tsx | 51 ++++--- .../sessionRecordingsPlaylistLogic.ts | 73 +++++++++- frontend/src/types.ts | 12 ++ 9 files changed, 243 insertions(+), 25 deletions(-) create mode 100644 frontend/__snapshots__/filters-universalfilters--default--dark.png create mode 100644 frontend/__snapshots__/filters-universalfilters--default--light.png create mode 100644 frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx diff --git a/frontend/__snapshots__/filters-universalfilters--default--dark.png b/frontend/__snapshots__/filters-universalfilters--default--dark.png new file mode 100644 index 0000000000000000000000000000000000000000..9d5153cb3fe5c231cad336f1b41bde2c807885a5 GIT binary patch literal 7286 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 literal 0 HcmV?d00001 diff --git a/frontend/__snapshots__/scenes-app-insights--trends-line-edit--light.png b/frontend/__snapshots__/scenes-app-insights--trends-line-edit--light.png index 0510a04db24d3c4796ebbed9ecbcb5fea5b320d7..fb9a44bbb9c0919b20de8046088e5a3084b6b75f 100644 GIT binary patch delta 112924 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 delta 100962 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

X1!S0xt7`SP7iEYD*3h&-c zLjc(hdPg~lT4Dz&=p=}i_YS;tn^=V`SBI#|&8PPT+* zaClE^ya>2uT4-7Z}x7y6Q7q$}qak;4uZFT!XvJFq{I^Y(sj$&J3b*`TVWP}6f=7Ix=T^gXbsWz>b@!Cmup89fmZN3ff>zTf7M^DcPuEnkXtO@bCfy{ z6iWk^vKXs^F(o1>u6;K8<{)|h<-u>Jgg23t*vSl&G(eG}C`g3oOj1ovFs5DhTglrB zqElzGe8^8+w4x^kd&;X-Sxa}xWw7GEYro;5{u-zK6EW0}cn*a5;K>z&zk2wE}wkYZ!^hGTNu5`&b z9xkp`Yt)YXq_?!b|FDhf@5QPg1r0c RAvj!WN9jk>51sw_KLBaa5zYVr diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index cdca3add1b2cd5..9dc990d211635e 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -206,6 +206,7 @@ export const FEATURE_FLAGS = { SETTINGS_PERSONS_JOIN_MODE: 'settings-persons-join-mode', // owner: @robbie-c HOG: 'hog', // owner: @mariusandra PERSONLESS_EVENTS_NOT_SUPPORTED: 'personless-events-not-supported', // owner: @raquelmsmith + SESSION_REPLAY_UNIVERSAL_FILTERS: 'session-replay-universal-filters', // owner: #team-replay ALERTS: 'alerts', // owner: github.com/nikitaevg SETTINGS_BOUNCE_RATE_PAGE_VIEW_MODE: 'settings-bounce-rate-page-view-mode', // owner: @robbie-c } as const diff --git a/frontend/src/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect.tsx b/frontend/src/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect.tsx index cde744f031014a..2d3ef56c692727 100644 --- a/frontend/src/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect.tsx +++ b/frontend/src/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect.tsx @@ -1,4 +1,4 @@ -import { LemonSelect } from '@posthog/lemon-ui' +import { LemonButtonProps, LemonSelect } from '@posthog/lemon-ui' import { FilterLogicalOperator } from '~/types' @@ -8,6 +8,7 @@ interface AndOrFilterSelectProps { topLevelFilter?: boolean prefix?: React.ReactNode suffix?: [singular: string, plural: string] + disabledReason?: LemonButtonProps['disabledReason'] } export function AndOrFilterSelect({ @@ -16,6 +17,7 @@ export function AndOrFilterSelect({ topLevelFilter, prefix = 'Match', suffix = ['filter in this group', 'filters in this group'], + disabledReason, }: AndOrFilterSelectProps): JSX.Element { return (

@@ -25,6 +27,7 @@ export function AndOrFilterSelect({ size="small" value={value} onChange={(type) => onChange(type as FilterLogicalOperator)} + disabledReason={disabledReason} options={[ { label: 'all', diff --git a/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx b/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx new file mode 100644 index 00000000000000..b43b5f3c550b6f --- /dev/null +++ b/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx @@ -0,0 +1,126 @@ +import { useActions, useMountedLogic, useValues } from 'kea' +import { DateFilter } from 'lib/components/DateFilter/DateFilter' +import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types' +import UniversalFilters from 'lib/components/UniversalFilters/UniversalFilters' +import { universalFiltersLogic } from 'lib/components/UniversalFilters/universalFiltersLogic' +import { isUniversalGroupFilterLike } from 'lib/components/UniversalFilters/utils' +import { TestAccountFilter } from 'scenes/insights/filters/TestAccountFilter' + +import { actionsModel } from '~/models/actionsModel' +import { cohortsModel } from '~/models/cohortsModel' +import { AndOrFilterSelect } from '~/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect' + +import { sessionRecordingsPlaylistLogic } from '../playlist/sessionRecordingsPlaylistLogic' + +export const RecordingsUniversalFilters = (): JSX.Element => { + useMountedLogic(cohortsModel) + useMountedLogic(actionsModel) + const { universalFilters } = useValues(sessionRecordingsPlaylistLogic) + const { setUniversalFilters } = useActions(sessionRecordingsPlaylistLogic) + + return ( +
+
+
+ { + setUniversalFilters({ + ...universalFilters, + date_from: changedDateFrom, + date_to: changedDateTo, + }) + }} + dateOptions={[ + { key: 'Custom', values: [] }, + { key: 'Last 24 hours', values: ['-24h'] }, + { key: 'Last 3 days', values: ['-3d'] }, + { key: 'Last 7 days', values: ['-7d'] }, + { key: 'Last 30 days', values: ['-30d'] }, + { key: 'All time', values: ['-90d'] }, + ]} + dropdownPlacement="bottom-start" + size="small" + /> + + setUniversalFilters({ + ...universalFilters, + filter_test_accounts: testFilters.filter_test_accounts, + }) + } + /> +
+
+ { + setUniversalFilters({ + ...universalFilters, + filter_group: { + type: type, + values: universalFilters.filter_group.values, + }, + }) + }} + disabledReason="'Or' filtering is not supported yet" + topLevelFilter={true} + suffix={['filter', 'filters']} + /> +
+
+
+ { + setUniversalFilters({ + ...universalFilters, + filter_group: filterGroup, + }) + }} + > + + + +
+
+ ) +} + +const RecordingsUniversalFilterGroup = (): JSX.Element => { + const { filterGroup } = useValues(universalFiltersLogic) + const { replaceGroupValue, removeGroupValue } = useActions(universalFiltersLogic) + + return ( + <> + {filterGroup.values.map((filterOrGroup, index) => { + return isUniversalGroupFilterLike(filterOrGroup) ? ( + + + + ) : ( + removeGroupValue(index)} + onChange={(value) => replaceGroupValue(index, value)} + /> + ) + })} + + ) +} diff --git a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx index 40d3d356bd4474..17ae678e317921 100644 --- a/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx +++ b/frontend/src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx @@ -23,6 +23,7 @@ import { urls } from 'scenes/urls' import { ReplayTabs, SessionRecordingType } from '~/types' +import { RecordingsUniversalFilters } from '../filters/RecordingsUniversalFilters' import { SessionRecordingsFilters } from '../filters/SessionRecordingsFilters' import { SessionRecordingPlayer } from '../player/SessionRecordingPlayer' import { SessionRecordingPreview, SessionRecordingPreviewSkeleton } from './SessionRecordingPreview' @@ -118,6 +119,7 @@ function RecordingsLists(): JSX.Element { recordingsCount, isRecordingsListCollapsed, sessionSummaryLoading, + useUniversalFiltering, } = useValues(sessionRecordingsPlaylistLogic) const { setSelectedRecordingId, @@ -205,25 +207,27 @@ function RecordingsLists(): JSX.Element { - - - - } - onClick={() => { - if (notebookNode) { - notebookNode.actions.toggleEditing() - } else { - setShowFilters(!showFilters) + {(!useUniversalFiltering || notebookNode) && ( + + + } - }} - > - Filter - + onClick={() => { + if (notebookNode) { + notebookNode.actions.toggleEditing() + } else { + setShowFilters(!showFilters) + } + }} + > + Filter + + )} - + +
+ {useUniversalFiltering && } +
- - +
+
) } diff --git a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts index 6f128876501c8c..72bdc8f8bb7706 100644 --- a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts +++ b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts @@ -4,6 +4,10 @@ import { loaders } from 'kea-loaders' import { actionToUrl, router, urlToAction } from 'kea-router' import { subscriptions } from 'kea-subscriptions' import api from 'lib/api' +import { isAnyPropertyfilter } from 'lib/components/PropertyFilters/utils' +import { UniversalFiltersGroup, UniversalFilterValue } from 'lib/components/UniversalFilters/UniversalFilters' +import { DEFAULT_UNIVERSAL_GROUP_FILTER } from 'lib/components/UniversalFilters/universalFiltersLogic' +import { isActionFilter, isEventFilter } from 'lib/components/UniversalFilters/utils' import { FEATURE_FLAGS } from 'lib/constants' import { now } from 'lib/dayjs' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' @@ -12,11 +16,14 @@ import { eventUsageLogic } from 'lib/utils/eventUsageLogic' import posthog from 'posthog-js' import { + AnyPropertyFilter, DurationType, + FilterType, PropertyFilterType, PropertyOperator, RecordingDurationFilter, RecordingFilters, + RecordingUniversalFilters, ReplayTabs, SessionRecordingId, SessionRecordingsResponse, @@ -85,6 +92,13 @@ export const DEFAULT_RECORDING_FILTERS: RecordingFilters = { console_search_query: '', } +export const DEFAULT_RECORDING_UNIVERSAL_FILTERS: RecordingUniversalFilters = { + live_mode: false, + filter_test_accounts: false, + date_from: '-3d', + filter_group: { ...DEFAULT_UNIVERSAL_GROUP_FILTER }, +} + const DEFAULT_PERSON_RECORDING_FILTERS: RecordingFilters = { ...DEFAULT_RECORDING_FILTERS, date_from: '-30d', @@ -106,6 +120,33 @@ const capturePartialFilters = (filters: Partial): void => { ...partialFilters, }) } +function convertUniversalFiltersToLegacyFilters(universalFilters: RecordingUniversalFilters): RecordingFilters { + const nestedFilters = universalFilters.filter_group.values[0] as UniversalFiltersGroup + const filters = nestedFilters.values as UniversalFilterValue[] + + const properties: AnyPropertyFilter[] = [] + const events: FilterType['events'] = [] + const actions: FilterType['actions'] = [] + + filters.forEach((f) => { + if (isEventFilter(f)) { + events.push(f) + } else if (isActionFilter(f)) { + actions.push(f) + } else if (isAnyPropertyfilter(f)) { + properties.push(f) + } + }) + + // TODO: add console log and duration filtering (not yet supported in universal filtering) + + return { + ...universalFilters, + properties, + events, + actions, + } +} export interface SessionRecordingPlaylistLogicProps { logicKey?: string @@ -113,6 +154,7 @@ export interface SessionRecordingPlaylistLogicProps { updateSearchParams?: boolean autoPlay?: boolean hideSimpleFilters?: boolean + universalFilters?: RecordingUniversalFilters advancedFilters?: RecordingFilters simpleFilters?: RecordingFilters onFiltersChange?: (filters: RecordingFilters) => void @@ -148,6 +190,7 @@ export const sessionRecordingsPlaylistLogic = kea) => ({ filters }), setAdvancedFilters: (filters: Partial) => ({ filters }), setSimpleFilters: (filters: SimpleFiltersType) => ({ filters }), setShowFilters: (showFilters: boolean) => ({ showFilters }), @@ -355,6 +398,18 @@ export const sessionRecordingsPlaylistLogic = kea getDefaultFilters(props.personUUID), }, ], + universalFilters: [ + props.universalFilters ?? DEFAULT_RECORDING_UNIVERSAL_FILTERS, + { + setUniversalFilters: (state, { filters }) => { + return { + ...state, + ...filters, + } + }, + resetFilters: () => DEFAULT_RECORDING_UNIVERSAL_FILTERS, + }, + ], showFilters: [ true, { @@ -465,6 +520,12 @@ export const sessionRecordingsPlaylistLogic = kea { + actions.loadSessionRecordings() + props.onFiltersChange?.(values.filters) + capturePartialFilters(filters) + actions.loadEventsHaveSessionId() + }, setOrderBy: () => { actions.loadSessionRecordings() @@ -512,12 +573,20 @@ export const sessionRecordingsPlaylistLogic = kea [s.featureFlags], (featureFlags) => !!featureFlags[FEATURE_FLAGS.SESSION_REPLAY_HOG_QL_FILTERING], ], + useUniversalFiltering: [ + (s) => [s.featureFlags], + (featureFlags) => !!featureFlags[FEATURE_FLAGS.SESSION_REPLAY_UNIVERSAL_FILTERS], + ], logicProps: [() => [(_, props) => props], (props): SessionRecordingPlaylistLogicProps => props], filters: [ - (s) => [s.simpleFilters, s.advancedFilters], - (simpleFilters, advancedFilters): RecordingFilters => { + (s) => [s.simpleFilters, s.advancedFilters, s.universalFilters, s.featureFlags], + (simpleFilters, advancedFilters, universalFilters, featureFlags): RecordingFilters => { + if (featureFlags[FEATURE_FLAGS.SESSION_REPLAY_UNIVERSAL_FILTERS]) { + return convertUniversalFiltersToLegacyFilters(universalFilters) + } + return { ...advancedFilters, events: [...(simpleFilters?.events || []), ...(advancedFilters?.events || [])], diff --git a/frontend/src/types.ts b/frontend/src/types.ts index ab2fb248d8fd78..c6f41edc477164 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -5,6 +5,7 @@ import { ChartDataset, ChartType, InteractionItem } from 'chart.js' import { LogicWrapper } from 'kea' import { DashboardCompatibleScenes } from 'lib/components/SceneDashboardChoice/sceneDashboardChoiceModalLogic' import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types' +import { UniversalFiltersGroup } from 'lib/components/UniversalFilters/UniversalFilters' import { BIN_COUNT_AUTO, DashboardPrivilegeLevel, @@ -973,6 +974,17 @@ export interface RecordingFilters { filter_test_accounts?: boolean } +export interface RecordingUniversalFilters { + /** + * live mode is front end only, sets date_from and date_to to the last hour + */ + live_mode?: boolean + date_from?: string | null + date_to?: string | null + filter_test_accounts?: boolean + filter_group: UniversalFiltersGroup +} + export interface SessionRecordingsResponse { results: SessionRecordingType[] has_next: boolean From 030e296c9eea500809f904765b978e0048abd3d2 Mon Sep 17 00:00:00 2001 From: Frank Hamand Date: Mon, 10 Jun 2024 14:11:25 +0100 Subject: [PATCH 03/35] fix: add ph-no-capture to api token modal (#22756) --- frontend/src/lib/components/CodeSnippet/CodeSnippet.tsx | 9 ++++----- .../src/lib/components/CommandPalette/DebugCHQueries.tsx | 4 ++-- frontend/src/scenes/data-warehouse/ViewLinkModal.tsx | 2 +- .../src/scenes/settings/user/personalAPIKeysLogic.tsx | 4 +++- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/frontend/src/lib/components/CodeSnippet/CodeSnippet.tsx b/frontend/src/lib/components/CodeSnippet/CodeSnippet.tsx index ec1a6ca6f16082..cf7033aff1046b 100644 --- a/frontend/src/lib/components/CodeSnippet/CodeSnippet.tsx +++ b/frontend/src/lib/components/CodeSnippet/CodeSnippet.tsx @@ -5,7 +5,7 @@ import clsx from 'clsx' import { useValues } from 'kea' import { LemonButton } from 'lib/lemon-ui/LemonButton' import { copyToClipboard } from 'lib/utils/copyToClipboard' -import { CSSProperties, useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter' import bash from 'react-syntax-highlighter/dist/esm/languages/prism/bash' import dart from 'react-syntax-highlighter/dist/esm/languages/prism/dart' @@ -81,7 +81,7 @@ export interface CodeSnippetProps { wrap?: boolean compact?: boolean actions?: JSX.Element - style?: CSSProperties + className?: string /** What is being copied. @example 'link' */ thing?: string /** If set, the snippet becomes expandable when there's more than this number of lines. */ @@ -93,7 +93,7 @@ export function CodeSnippet({ language = Language.Text, wrap = false, compact = false, - style, + className, actions, thing = 'snippet', maxLinesWithoutExpansion, @@ -120,8 +120,7 @@ export function CodeSnippet({ } return ( - // eslint-disable-next-line react/forbid-dom-props -
+
{actions} {item.query} @@ -263,7 +263,7 @@ function DebugCHQueries(): JSX.Element { language={Language.JSON} maxLinesWithoutExpansion={0} key={item.query_id} - style={{ fontSize: 12, marginBottom: '0.25rem' }} + className="text-sm mb-2" > {JSON.stringify(event, null, 2)} diff --git a/frontend/src/scenes/data-warehouse/ViewLinkModal.tsx b/frontend/src/scenes/data-warehouse/ViewLinkModal.tsx index 5a01206bd52cff..df06fc5df3054e 100644 --- a/frontend/src/scenes/data-warehouse/ViewLinkModal.tsx +++ b/frontend/src/scenes/data-warehouse/ViewLinkModal.tsx @@ -184,7 +184,7 @@ export function ViewLinkForm(): JSX.Element {
- + {sqlCodeSnippet}
diff --git a/frontend/src/scenes/settings/user/personalAPIKeysLogic.tsx b/frontend/src/scenes/settings/user/personalAPIKeysLogic.tsx index d0da4e1b4f7ee9..caf5e068893469 100644 --- a/frontend/src/scenes/settings/user/personalAPIKeysLogic.tsx +++ b/frontend/src/scenes/settings/user/personalAPIKeysLogic.tsx @@ -306,7 +306,9 @@ export const personalAPIKeysLogic = kea([ <>

You can now use key "{key.label}" for authentication:

- {value} + + {value} + For security reasons the value above will never be shown again. From 95a0b858485eec96c4c8a69cce84ffc026d8acbf Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 10 Jun 2024 14:12:04 +0100 Subject: [PATCH 04/35] feat: DeliveryHog (#22706) --- docker-compose.dev-full.yml | 8 + docker-compose.dev.yml | 8 + frontend/src/lib/api.ts | 27 ++ frontend/src/lib/constants.tsx | 1 + frontend/src/scenes/actions/Action.tsx | 3 - frontend/src/scenes/actions/ActionPlugins.tsx | 67 --- frontend/src/scenes/pipeline/Destinations.tsx | 10 +- .../pipeline/PipelineNodeConfiguration.tsx | 5 +- .../src/scenes/pipeline/PipelineNodeNew.tsx | 87 ++-- .../src/scenes/pipeline/destinationsLogic.tsx | 71 +++- .../hogfunctions/HogFunctionInputs.tsx | 287 +++++++++++++ .../hogfunctions/HogFunctionInputsEditor.tsx | 116 ++++++ .../PipelineHogFunctionConfiguration.tsx | 247 +++++++++++ .../pipelineHogFunctionConfigurationLogic.tsx | 250 ++++++++++++ .../hogfunctions/templates/hog-templates.tsx | 58 +++ .../src/scenes/pipeline/pipelineNodeLogic.tsx | 23 +- .../scenes/pipeline/pipelineNodeNewLogic.tsx | 8 +- frontend/src/scenes/pipeline/types.ts | 30 +- frontend/src/scenes/scenes.ts | 2 +- frontend/src/scenes/urls.ts | 4 +- frontend/src/types.ts | 38 ++ hogvm/typescript/package.json | 8 +- hogvm/typescript/src/execute.ts | 10 +- hogvm/typescript/src/index.ts | 3 + latest_migrations.manifest | 2 +- plugin-server/package.json | 1 + plugin-server/pnpm-lock.yaml | 7 + plugin-server/src/capabilities.ts | 7 + .../src/cdp/cdp-processed-events-consumer.ts | 258 ++++++++++++ plugin-server/src/cdp/hog-executor.ts | 299 ++++++++++++++ plugin-server/src/cdp/hog-function-manager.ts | 132 ++++++ plugin-server/src/cdp/types.ts | 147 +++++++ plugin-server/src/cdp/utils.ts | 93 +++++ .../session-recordings-consumer.ts | 12 +- plugin-server/src/main/pluginsServer.ts | 30 +- plugin-server/src/types.ts | 2 + plugin-server/src/utils/db/hub.ts | 7 +- plugin-server/src/worker/rusty-hook.ts | 23 +- .../cdp/cdp-processed-events-consumer.test.ts | 146 +++++++ plugin-server/tests/cdp/examples.ts | 161 ++++++++ plugin-server/tests/cdp/fixtures.ts | 93 +++++ plugin-server/tests/cdp/hog-executor.test.ts | 127 ++++++ pnpm-lock.yaml | 2 +- posthog/api/__init__.py | 8 + posthog/api/app_metrics.py | 21 +- posthog/api/hog_function.py | 180 ++++++++ .../api/test/__snapshots__/test_decide.ambr | 385 ++++++++++++++++-- posthog/api/test/test_hog_function.py | 287 +++++++++++++ posthog/migrations/0425_hogfunction.py | 47 +++ posthog/models/__init__.py | 2 + posthog/models/hog_functions/__init__.py | 1 + posthog/models/hog_functions/hog_function.py | 118 ++++++ posthog/models/hog_functions/utils.py | 66 +++ posthog/models/test/test_hog_function.py | 283 +++++++++++++ posthog/permissions.py | 35 ++ posthog/test/test_migration_0410.py | 67 --- production.Dockerfile | 2 +- requirements-dev.in | 1 + requirements-dev.txt | 45 +- 59 files changed, 4154 insertions(+), 314 deletions(-) delete mode 100644 frontend/src/scenes/actions/ActionPlugins.tsx create mode 100644 frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx create mode 100644 frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputsEditor.tsx create mode 100644 frontend/src/scenes/pipeline/hogfunctions/PipelineHogFunctionConfiguration.tsx create mode 100644 frontend/src/scenes/pipeline/hogfunctions/pipelineHogFunctionConfigurationLogic.tsx create mode 100644 frontend/src/scenes/pipeline/hogfunctions/templates/hog-templates.tsx create mode 100644 hogvm/typescript/src/index.ts create mode 100644 plugin-server/src/cdp/cdp-processed-events-consumer.ts create mode 100644 plugin-server/src/cdp/hog-executor.ts create mode 100644 plugin-server/src/cdp/hog-function-manager.ts create mode 100644 plugin-server/src/cdp/types.ts create mode 100644 plugin-server/src/cdp/utils.ts create mode 100644 plugin-server/tests/cdp/cdp-processed-events-consumer.test.ts create mode 100644 plugin-server/tests/cdp/examples.ts create mode 100644 plugin-server/tests/cdp/fixtures.ts create mode 100644 plugin-server/tests/cdp/hog-executor.test.ts create mode 100644 posthog/api/hog_function.py create mode 100644 posthog/api/test/test_hog_function.py create mode 100644 posthog/migrations/0425_hogfunction.py create mode 100644 posthog/models/hog_functions/__init__.py create mode 100644 posthog/models/hog_functions/hog_function.py create mode 100644 posthog/models/hog_functions/utils.py create mode 100644 posthog/models/test/test_hog_function.py delete mode 100644 posthog/test/test_migration_0410.py diff --git a/docker-compose.dev-full.yml b/docker-compose.dev-full.yml index cdb2eee4ec2855..002553728d815d 100644 --- a/docker-compose.dev-full.yml +++ b/docker-compose.dev-full.yml @@ -71,6 +71,14 @@ services: - '1080:1080' - '1025:1025' + webhook-tester: + image: tarampampam/webhook-tester:1.1.0 + restart: on-failure + ports: + - '2080:2080' + environment: + - PORT=2080 + worker: extends: file: docker-compose.base.yml diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 25d30840b83ee4..e45e3c3997ea9b 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -95,6 +95,14 @@ services: - '1080:1080' - '1025:1025' + webhook-tester: + image: tarampampam/webhook-tester:1.1.0 + restart: on-failure + ports: + - '2080:2080' + environment: + - LISTEN_PORT=2080 + # Optional capture capture: profiles: ['capture-rs'] diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 3f44c3b06eeb08..d83402b59c88d1 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -43,6 +43,7 @@ import { FeatureFlagType, Group, GroupListParams, + HogFunctionType, InsightModel, IntegrationType, ListOrganizationMembersParams, @@ -320,6 +321,14 @@ class ApiRequest { return this.pluginConfig(pluginConfigId, teamId).addPathComponent('logs') } + public hogFunctions(teamId?: TeamType['id']): ApiRequest { + return this.projectsDetail(teamId).addPathComponent('hog_functions') + } + + public hogFunction(id: HogFunctionType['id'], teamId?: TeamType['id']): ApiRequest { + return this.hogFunctions(teamId).addPathComponent(id) + } + // # Actions public actions(teamId?: TeamType['id']): ApiRequest { return this.projectsDetail(teamId).addPathComponent('actions') @@ -1634,6 +1643,24 @@ const api = { }, }, + hogFunctions: { + async listTemplates(): Promise> { + return await new ApiRequest().hogFunctions().get() + }, + async list(): Promise> { + return await new ApiRequest().hogFunctions().get() + }, + async get(id: HogFunctionType['id']): Promise { + return await new ApiRequest().hogFunction(id).get() + }, + async create(data: Partial): Promise { + return await new ApiRequest().hogFunctions().create({ data }) + }, + async update(id: HogFunctionType['id'], data: Partial): Promise { + return await new ApiRequest().hogFunction(id).update({ data }) + }, + }, + annotations: { async get(annotationId: RawAnnotationType['id']): Promise { return await new ApiRequest().annotation(annotationId).get() diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index 9dc990d211635e..0d22661936fee6 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -205,6 +205,7 @@ export const FEATURE_FLAGS = { SESSION_REPLAY_NETWORK_VIEW: 'session-replay-network-view', // owner: #team-replay SETTINGS_PERSONS_JOIN_MODE: 'settings-persons-join-mode', // owner: @robbie-c HOG: 'hog', // owner: @mariusandra + HOG_FUNCTIONS: 'hog-functions', // owner: #team-cdp PERSONLESS_EVENTS_NOT_SUPPORTED: 'personless-events-not-supported', // owner: @raquelmsmith SESSION_REPLAY_UNIVERSAL_FILTERS: 'session-replay-universal-filters', // owner: #team-replay ALERTS: 'alerts', // owner: github.com/nikitaevg diff --git a/frontend/src/scenes/actions/Action.tsx b/frontend/src/scenes/actions/Action.tsx index 05080a78324e42..5ff2bdc067eb07 100644 --- a/frontend/src/scenes/actions/Action.tsx +++ b/frontend/src/scenes/actions/Action.tsx @@ -11,7 +11,6 @@ import { NodeKind } from '~/queries/schema' import { ActionType } from '~/types' import { ActionEdit } from './ActionEdit' -import { ActionPlugins } from './ActionPlugins' export const scene: SceneExport = { logic: actionLogic, @@ -47,8 +46,6 @@ export function Action({ id }: { id?: ActionType['id'] } = {}): JSX.Element { {id && ( <> - - {isComplete ? (

Matching events

diff --git a/frontend/src/scenes/actions/ActionPlugins.tsx b/frontend/src/scenes/actions/ActionPlugins.tsx deleted file mode 100644 index 56d6807cf35a8c..00000000000000 --- a/frontend/src/scenes/actions/ActionPlugins.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { LemonButton, LemonTable } from '@posthog/lemon-ui' -import { useActions, useValues } from 'kea' -import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink' -import { useEffect } from 'react' -import { actionLogic } from 'scenes/actions/actionLogic' -import { PluginImage } from 'scenes/plugins/plugin/PluginImage' -import { urls } from 'scenes/urls' - -import { PipelineNodeTab, PipelineStage } from '~/types' - -export function ActionPlugins(): JSX.Element | null { - const { matchingPluginConfigs } = useValues(actionLogic) - const { loadMatchingPluginConfigs } = useActions(actionLogic) - - useEffect(() => { - loadMatchingPluginConfigs() - }, []) - - if (!matchingPluginConfigs?.length) { - return null - } - - return ( - <> -

Connected data pipelines

- - ( -
- - -
- ), - }, - { - title: '', - width: 0, - render: (_, config) => ( - - Configure - - ), - }, - ]} - /> - - ) -} diff --git a/frontend/src/scenes/pipeline/Destinations.tsx b/frontend/src/scenes/pipeline/Destinations.tsx index 61af57f3061c56..500111bdc5d1bd 100644 --- a/frontend/src/scenes/pipeline/Destinations.tsx +++ b/frontend/src/scenes/pipeline/Destinations.tsx @@ -57,10 +57,14 @@ export function DestinationsTable({ inOverview = false }: { inOverview?: boolean title: 'App', width: 0, render: function RenderAppInfo(_, destination) { - if (destination.backend === 'plugin') { - return + switch (destination.backend) { + case 'plugin': + return + case 'batch_export': + return + default: + return null } - return }, }, { diff --git a/frontend/src/scenes/pipeline/PipelineNodeConfiguration.tsx b/frontend/src/scenes/pipeline/PipelineNodeConfiguration.tsx index 61d09e36015c40..926469a85ffd6f 100644 --- a/frontend/src/scenes/pipeline/PipelineNodeConfiguration.tsx +++ b/frontend/src/scenes/pipeline/PipelineNodeConfiguration.tsx @@ -1,6 +1,7 @@ import { useValues } from 'kea' import { NotFound } from 'lib/components/NotFound' +import { PipelineHogFunctionConfiguration } from './hogfunctions/PipelineHogFunctionConfiguration' import { PipelineBatchExportConfiguration } from './PipelineBatchExportConfiguration' import { pipelineNodeLogic } from './pipelineNodeLogic' import { PipelinePluginConfiguration } from './PipelinePluginConfiguration' @@ -15,7 +16,9 @@ export function PipelineNodeConfiguration(): JSX.Element { return (
- {node.backend === PipelineBackend.Plugin ? ( + {node.backend === PipelineBackend.HogFunction ? ( + + ) : node.backend === PipelineBackend.Plugin ? ( ) : ( diff --git a/frontend/src/scenes/pipeline/PipelineNodeNew.tsx b/frontend/src/scenes/pipeline/PipelineNodeNew.tsx index 30504da3fe51de..76eea7518a6b07 100644 --- a/frontend/src/scenes/pipeline/PipelineNodeNew.tsx +++ b/frontend/src/scenes/pipeline/PipelineNodeNew.tsx @@ -1,17 +1,20 @@ import { IconPlusSmall } from '@posthog/icons' import { useValues } from 'kea' +import { combineUrl, router } from 'kea-router' import { NotFound } from 'lib/components/NotFound' import { PayGateMini } from 'lib/components/PayGateMini/PayGateMini' +import { useFeatureFlag } from 'lib/hooks/useFeatureFlag' import { LemonButton } from 'lib/lemon-ui/LemonButton' import { LemonTable } from 'lib/lemon-ui/LemonTable' import { LemonTableLink } from 'lib/lemon-ui/LemonTable/LemonTableLink' import { SceneExport } from 'scenes/sceneTypes' import { urls } from 'scenes/urls' -import { AvailableFeature, BatchExportService, PipelineStage, PluginType } from '~/types' +import { AvailableFeature, BatchExportService, HogFunctionTemplateType, PipelineStage, PluginType } from '~/types' import { pipelineDestinationsLogic } from './destinationsLogic' import { frontendAppsLogic } from './frontendAppsLogic' +import { PipelineHogFunctionConfiguration } from './hogfunctions/PipelineHogFunctionConfiguration' import { PipelineBatchExportConfiguration } from './PipelineBatchExportConfiguration' import { PIPELINE_TAB_TO_NODE_STAGE } from './PipelineNode' import { pipelineNodeNewLogic, PipelineNodeNewLogicProps } from './pipelineNodeNewLogic' @@ -21,21 +24,20 @@ import { PipelineBackend } from './types' import { getBatchExportUrl, RenderApp, RenderBatchExportIcon } from './utils' const paramsToProps = ({ - params: { stage, pluginIdOrBatchExportDestination }, + params: { stage, id }, }: { - params: { stage?: string; pluginIdOrBatchExportDestination?: string } + params: { stage?: string; id?: string } }): PipelineNodeNewLogicProps => { - const numericId = - pluginIdOrBatchExportDestination && /^\d+$/.test(pluginIdOrBatchExportDestination) - ? parseInt(pluginIdOrBatchExportDestination) - : undefined + const numericId = id && /^\d+$/.test(id) ? parseInt(id) : undefined const pluginId = numericId && !isNaN(numericId) ? numericId : null - const batchExportDestination = pluginId ? null : pluginIdOrBatchExportDestination ?? null + const hogFunctionId = pluginId ? null : id?.startsWith('hog-') ? id.slice(4) : null + const batchExportDestination = hogFunctionId ? null : id ?? null return { stage: PIPELINE_TAB_TO_NODE_STAGE[stage + 's'] || null, // pipeline tab has stage plural here we have singular - pluginId: pluginId, - batchExportDestination: batchExportDestination, + pluginId, + batchExportDestination, + hogFunctionId, } } @@ -45,32 +47,22 @@ export const scene: SceneExport = { paramsToProps, } -type PluginEntry = { - backend: PipelineBackend.Plugin - id: number +type TableEntry = { + backend: PipelineBackend + id: string | number name: string description: string - plugin: PluginType url?: string + icon: JSX.Element } -type BatchExportEntry = { - backend: PipelineBackend.BatchExport - id: BatchExportService['type'] - name: string - description: string - url: string -} - -type TableEntry = PluginEntry | BatchExportEntry - function convertPluginToTableEntry(plugin: PluginType): TableEntry { return { backend: PipelineBackend.Plugin, id: plugin.id, name: plugin.name, description: plugin.description || '', - plugin: plugin, + icon: , // TODO: ideally we'd link to docs instead of GitHub repo, so it can open in panel // Same for transformations and destinations tables url: plugin.url, @@ -80,17 +72,26 @@ function convertPluginToTableEntry(plugin: PluginType): TableEntry { function convertBatchExportToTableEntry(service: BatchExportService['type']): TableEntry { return { backend: PipelineBackend.BatchExport, - id: service, + id: service as string, name: service, description: `${service} batch export`, + icon: , url: getBatchExportUrl(service), } } -export function PipelineNodeNew( - params: { stage?: string; pluginIdOrBatchExportDestination?: string } = {} -): JSX.Element { - const { stage, pluginId, batchExportDestination } = paramsToProps({ params }) +function convertHogFunctionToTableEntry(hogFunction: HogFunctionTemplateType): TableEntry { + return { + backend: PipelineBackend.HogFunction, + id: `hog-${hogFunction.id}`, // TODO: This weird identifier thing isn't great + name: hogFunction.name, + description: hogFunction.description, + icon: 🦔, + } +} + +export function PipelineNodeNew(params: { stage?: string; id?: string } = {}): JSX.Element { + const { stage, pluginId, batchExportDestination, hogFunctionId } = paramsToProps({ params }) if (!stage) { return @@ -103,6 +104,7 @@ export function PipelineNodeNew( } return res } + if (batchExportDestination) { if (stage !== PipelineStage.Destination) { return @@ -114,6 +116,14 @@ export function PipelineNodeNew( ) } + if (hogFunctionId) { + const res = + if (stage === PipelineStage.Destination) { + return {res} + } + return res + } + if (stage === PipelineStage.Transformation) { return } else if (stage === PipelineStage.Destination) { @@ -135,11 +145,15 @@ function TransformationOptionsTable(): JSX.Element { } function DestinationOptionsTable(): JSX.Element { + const hogFunctionsEnabled = !!useFeatureFlag('HOG_FUNCTIONS') const { batchExportServiceNames } = useValues(pipelineNodeNewLogic) - const { plugins, loading } = useValues(pipelineDestinationsLogic) + const { plugins, loading, hogFunctionTemplates } = useValues(pipelineDestinationsLogic) const pluginTargets = Object.values(plugins).map(convertPluginToTableEntry) const batchExportTargets = Object.values(batchExportServiceNames).map(convertBatchExportToTableEntry) - const targets = [...batchExportTargets, ...pluginTargets] + const hogFunctionTargets = hogFunctionsEnabled + ? Object.values(hogFunctionTemplates).map(convertHogFunctionToTableEntry) + : [] + const targets = [...batchExportTargets, ...pluginTargets, ...hogFunctionTargets] return } @@ -158,6 +172,7 @@ function NodeOptionsTable({ targets: TableEntry[] loading: boolean }): JSX.Element { + const { hashParams } = useValues(router) return ( <> - } - return + return target.icon }, }, { @@ -198,7 +210,8 @@ function NodeOptionsTable({ type="primary" data-attr={`new-${stage}-${target.id}`} icon={} - to={urls.pipelineNodeNew(stage, target.id)} + // Preserve hash params to pass config in + to={combineUrl(urls.pipelineNodeNew(stage, target.id), {}, hashParams).url} > Create diff --git a/frontend/src/scenes/pipeline/destinationsLogic.tsx b/frontend/src/scenes/pipeline/destinationsLogic.tsx index d50e6b5de5091b..05d4e2ff1d1e62 100644 --- a/frontend/src/scenes/pipeline/destinationsLogic.tsx +++ b/frontend/src/scenes/pipeline/destinationsLogic.tsx @@ -8,6 +8,8 @@ import { userLogic } from 'scenes/userLogic' import { BatchExportConfiguration, + HogFunctionTemplateType, + HogFunctionType, PipelineStage, PluginConfigTypeNew, PluginConfigWithPluginInfoNew, @@ -16,6 +18,7 @@ import { } from '~/types' import type { pipelineDestinationsLogicType } from './destinationsLogicType' +import { HOG_FUNCTION_TEMPLATES } from './hogfunctions/templates/hog-templates' import { pipelineAccessLogic } from './pipelineAccessLogic' import { BatchExportDestination, convertToPipelineNode, Destination, PipelineBackend } from './types' import { captureBatchExportEvent, capturePluginEvent, loadPluginsFromUrl } from './utils' @@ -116,28 +119,68 @@ export const pipelineDestinationsLogic = kea([ }, }, ], + + hogFunctionTemplates: [ + {} as Record, + { + loadHogFunctionTemplates: async () => { + return HOG_FUNCTION_TEMPLATES.reduce((acc, template) => { + acc[template.id] = template + return acc + }, {} as Record) + }, + }, + ], + hogFunctions: [ + [] as HogFunctionType[], + { + loadHogFunctions: async () => { + // TODO: Support pagination? + return (await api.hogFunctions.list()).results + }, + }, + ], })), selectors({ loading: [ - (s) => [s.pluginsLoading, s.pluginConfigsLoading, s.batchExportConfigsLoading], - (pluginsLoading, pluginConfigsLoading, batchExportConfigsLoading) => - pluginsLoading || pluginConfigsLoading || batchExportConfigsLoading, + (s) => [ + s.pluginsLoading, + s.pluginConfigsLoading, + s.batchExportConfigsLoading, + s.hogFunctionTemplatesLoading, + s.hogFunctionsLoading, + ], + ( + pluginsLoading, + pluginConfigsLoading, + batchExportConfigsLoading, + hogFunctionTemplatesLoading, + hogFunctionsLoading + ) => + pluginsLoading || + pluginConfigsLoading || + batchExportConfigsLoading || + hogFunctionTemplatesLoading || + hogFunctionsLoading, ], destinations: [ - (s) => [s.pluginConfigs, s.plugins, s.batchExportConfigs, s.user], - (pluginConfigs, plugins, batchExportConfigs, user): Destination[] => { + (s) => [s.pluginConfigs, s.plugins, s.batchExportConfigs, s.hogFunctions, s.user], + (pluginConfigs, plugins, batchExportConfigs, hogFunctions, user): Destination[] => { // Migrations are shown only in impersonation mode, for us to be able to trigger them. const rawBatchExports = Object.values(batchExportConfigs).filter( (config) => config.destination.type !== 'HTTP' || user?.is_impersonated ) - const rawDestinations: (PluginConfigWithPluginInfoNew | BatchExportConfiguration)[] = Object.values( - pluginConfigs - ) - .map((pluginConfig) => ({ - ...pluginConfig, - plugin_info: plugins[pluginConfig.plugin] || null, - })) - .concat(rawBatchExports) + + const rawDestinations: (PluginConfigWithPluginInfoNew | BatchExportConfiguration | HogFunctionType)[] = + Object.values(pluginConfigs) + .map( + (pluginConfig) => ({ + ...pluginConfig, + plugin_info: plugins[pluginConfig.plugin] || null, + }) + ) + .concat(rawBatchExports) + .concat(hogFunctions) const convertedDestinations = rawDestinations.map((d) => convertToPipelineNode(d, PipelineStage.Destination) ) @@ -183,5 +226,7 @@ export const pipelineDestinationsLogic = kea([ actions.loadPlugins() actions.loadPluginConfigs() actions.loadBatchExports() + actions.loadHogFunctionTemplates() + actions.loadHogFunctions() }), ]) diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx new file mode 100644 index 00000000000000..2f3cc63865729f --- /dev/null +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputs.tsx @@ -0,0 +1,287 @@ +import { Monaco } from '@monaco-editor/react' +import { IconPencil, IconPlus, IconX } from '@posthog/icons' +import { LemonButton, LemonCheckbox, LemonInput, LemonSelect } from '@posthog/lemon-ui' +import { useValues } from 'kea' +import { CodeEditor } from 'lib/components/CodeEditors' +import { languages } from 'monaco-editor' +import { useEffect, useMemo, useState } from 'react' + +import { groupsModel } from '~/models/groupsModel' +import { HogFunctionInputSchemaType } from '~/types' + +export type HogFunctionInputProps = { + schema: HogFunctionInputSchemaType + value?: any + onChange?: (value: any) => void + disabled?: boolean +} + +const SECRET_FIELD_VALUE = '********' + +function useAutocompleteOptions(): languages.CompletionItem[] { + const { groupTypes } = useValues(groupsModel) + + return useMemo(() => { + const options = [ + ['event', 'The entire event payload as a JSON object'], + ['event.name', 'The name of the event e.g. $pageview'], + ['event.distinct_id', 'The distinct_id of the event'], + ['event.timestamp', 'The timestamp of the event'], + ['event.url', 'URL to the event in PostHog'], + ['event.properties', 'Properties of the event'], + ['event.properties.', 'The individual property of the event'], + ['person', 'The entire person payload as a JSON object'], + ['project.uuid', 'The UUID of the Person in PostHog'], + ['person.url', 'URL to the person in PostHog'], + ['person.properties', 'Properties of the person'], + ['person.properties.', 'The individual property of the person'], + ['project.id', 'ID of the project in PostHog'], + ['project.name', 'Name of the project'], + ['project.url', 'URL to the project in PostHog'], + ['source.name', 'Name of the source of this message'], + ['source.url', 'URL to the source of this message in PostHog'], + ] + + groupTypes.forEach((groupType) => { + options.push([`groups.${groupType.group_type}`, `The entire group payload as a JSON object`]) + options.push([`groups.${groupType.group_type}.id`, `The ID or 'key' of the group`]) + options.push([`groups.${groupType.group_type}.url`, `URL to the group in PostHog`]) + options.push([`groups.${groupType.group_type}.properties`, `Properties of the group`]) + options.push([`groups.${groupType.group_type}.properties.`, `The individual property of the group`]) + options.push([`groups.${groupType.group_type}.index`, `Index of the group`]) + }) + + const items: languages.CompletionItem[] = options.map(([key, value]) => { + return { + label: key, + kind: languages.CompletionItemKind.Variable, + detail: value, + insertText: key, + range: { + startLineNumber: 1, + endLineNumber: 1, + startColumn: 0, + endColumn: 0, + }, + } + }) + + return items + }, [groupTypes]) +} + +function JsonConfigField(props: { + onChange?: (value: string) => void + className: string + autoFocus: boolean + value?: string | object +}): JSX.Element { + const suggestions = useAutocompleteOptions() + const [monaco, setMonaco] = useState() + + useEffect(() => { + if (!monaco) { + return + } + monaco.languages.setLanguageConfiguration('json', { + wordPattern: /[a-zA-Z0-9_\-.]+/, + }) + + const provider = monaco.languages.registerCompletionItemProvider('json', { + triggerCharacters: ['{', '{{'], + provideCompletionItems: async (model, position) => { + const word = model.getWordUntilPosition(position) + + const wordWithTrigger = model.getValueInRange({ + startLineNumber: position.lineNumber, + startColumn: 0, + endLineNumber: position.lineNumber, + endColumn: position.column, + }) + + if (wordWithTrigger.indexOf('{') === -1) { + return { suggestions: [] } + } + + const localSuggestions = suggestions.map((x) => ({ + ...x, + insertText: x.insertText, + range: { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: word.startColumn, + endColumn: word.endColumn, + }, + })) + + return { + suggestions: localSuggestions, + incomplete: false, + } + }, + }) + + return () => provider.dispose() + }, [suggestions, monaco]) + + return ( + props.onChange?.(v ?? '')} + options={{ + lineNumbers: 'off', + minimap: { + enabled: false, + }, + quickSuggestions: { + other: true, + strings: true, + }, + suggest: { + showWords: false, + showFields: false, + showKeywords: false, + }, + scrollbar: { + vertical: 'hidden', + verticalScrollbarSize: 0, + }, + }} + onMount={(_editor, monaco) => { + setMonaco(monaco) + }} + /> + ) +} + +function DictionaryField({ onChange, value }: { onChange?: (value: any) => void; value: any }): JSX.Element { + const [entries, setEntries] = useState<[string, string][]>(Object.entries(value ?? {})) + + useEffect(() => { + // NOTE: Filter out all empty entries as fetch will throw if passed in + const val = Object.fromEntries(entries.filter(([key, val]) => key.trim() !== '' || val.trim() !== '')) + onChange?.(val) + }, [entries]) + + return ( +
+ {entries.map(([key, val], index) => ( +
+ { + const newEntries = [...entries] + newEntries[index] = [key, newEntries[index][1]] + setEntries(newEntries) + }} + placeholder="Key" + /> + + { + const newEntries = [...entries] + newEntries[index] = [newEntries[index][0], val] + setEntries(newEntries) + }} + placeholder="Value" + /> + + } + size="small" + onClick={() => { + const newEntries = [...entries] + newEntries.splice(index, 1) + setEntries(newEntries) + }} + /> +
+ ))} + } + size="small" + type="secondary" + onClick={() => { + setEntries([...entries, ['', '']]) + }} + > + Add entry + +
+ ) +} + +export function HogFunctionInput({ value, onChange, schema, disabled }: HogFunctionInputProps): JSX.Element { + const [editingSecret, setEditingSecret] = useState(false) + if ( + schema.secret && + !editingSecret && + value && + (value === SECRET_FIELD_VALUE || value.name === SECRET_FIELD_VALUE) + ) { + return ( + } + onClick={() => { + onChange?.(schema.default || '') + setEditingSecret(true) + }} + disabled={disabled} + > + Reset secret variable + + ) + } + + switch (schema.type) { + case 'string': + return ( + + ) + case 'json': + return ( + + ) + case 'choice': + return ( + + ) + case 'dictionary': + return + + case 'boolean': + return onChange?.(checked)} disabled={disabled} /> + default: + return ( + + Unknown field type "{schema.type}". +
+ You may need to upgrade PostHog! +
+ ) + } +} diff --git a/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputsEditor.tsx b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputsEditor.tsx new file mode 100644 index 00000000000000..59bc0fbfffaa1e --- /dev/null +++ b/frontend/src/scenes/pipeline/hogfunctions/HogFunctionInputsEditor.tsx @@ -0,0 +1,116 @@ +import { IconPlus, IconX } from '@posthog/icons' +import { LemonButton, LemonCheckbox, LemonInput, LemonInputSelect, LemonSelect } from '@posthog/lemon-ui' +import { capitalizeFirstLetter } from 'kea-forms' +import { useEffect, useState } from 'react' + +import { HogFunctionInputSchemaType } from '~/types' + +const typeList = ['string', 'boolean', 'dictionary', 'choice', 'json'] as const + +export type HogFunctionInputsEditorProps = { + value?: HogFunctionInputSchemaType[] + onChange?: (value: HogFunctionInputSchemaType[]) => void +} + +export function HogFunctionInputsEditor({ value, onChange }: HogFunctionInputsEditorProps): JSX.Element { + const [inputs, setInputs] = useState(value ?? []) + + useEffect(() => { + onChange?.(inputs) + }, [inputs]) + + return ( +
+ {inputs.map((input, index) => { + const _onChange = (data: Partial): void => { + setInputs((inputs) => { + const newInputs = [...inputs] + newInputs[index] = { ...newInputs[index], ...data } + return newInputs + }) + } + + return ( +
+
+ _onChange({ key })} + placeholder="Variable name" + /> + ({ + label: capitalizeFirstLetter(type), + value: type, + }))} + value={input.type} + className="w-30" + onChange={(type) => _onChange({ type })} + /> + + _onChange({ label })} + placeholder="Display label" + /> + _onChange({ required })} + label="Required" + bordered + /> + _onChange({ secret })} + label="Secret" + bordered + /> + {input.type === 'choice' && ( + choice.value)} + onChange={(choices) => + _onChange({ choices: choices.map((value) => ({ label: value, value })) }) + } + placeholder="Choices" + /> + )} +
+ } + size="small" + onClick={() => { + const newInputs = [...inputs] + newInputs.splice(index, 1) + setInputs(newInputs) + }} + /> +
+ ) + })} + +
+ } + size="small" + type="secondary" + onClick={() => { + setInputs([ + ...inputs, + { type: 'string', key: `input_${inputs.length + 1}`, label: '', required: false }, + ]) + }} + > + Add input variable + +
+
+ ) +} diff --git a/frontend/src/scenes/pipeline/hogfunctions/PipelineHogFunctionConfiguration.tsx b/frontend/src/scenes/pipeline/hogfunctions/PipelineHogFunctionConfiguration.tsx new file mode 100644 index 00000000000000..caeda41d63eef4 --- /dev/null +++ b/frontend/src/scenes/pipeline/hogfunctions/PipelineHogFunctionConfiguration.tsx @@ -0,0 +1,247 @@ +import { LemonButton, LemonInput, LemonSwitch, LemonTextArea, SpinnerOverlay } from '@posthog/lemon-ui' +import { useActions, useValues } from 'kea' +import { Form } from 'kea-forms' +import { NotFound } from 'lib/components/NotFound' +import { PageHeader } from 'lib/components/PageHeader' +import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types' +import { TestAccountFilterSwitch } from 'lib/components/TestAccountFiltersSwitch' +import { useFeatureFlag } from 'lib/hooks/useFeatureFlag' +import { LemonField } from 'lib/lemon-ui/LemonField' +import { HogQueryEditor } from 'scenes/debug/HogDebug' +import { ActionFilter } from 'scenes/insights/filters/ActionFilter/ActionFilter' +import { MathAvailability } from 'scenes/insights/filters/ActionFilter/ActionFilterRow/ActionFilterRow' + +import { groupsModel } from '~/models/groupsModel' +import { NodeKind } from '~/queries/schema' +import { EntityTypes } from '~/types' + +import { HogFunctionInput } from './HogFunctionInputs' +import { HogFunctionInputsEditor } from './HogFunctionInputsEditor' +import { pipelineHogFunctionConfigurationLogic } from './pipelineHogFunctionConfigurationLogic' + +export function PipelineHogFunctionConfiguration({ + templateId, + id, +}: { + templateId?: string + id?: string +}): JSX.Element { + const logicProps = { templateId, id } + const logic = pipelineHogFunctionConfigurationLogic(logicProps) + const { isConfigurationSubmitting, configurationChanged, showSource, configuration, loading, loaded } = + useValues(logic) + const { submitConfiguration, resetForm, setShowSource } = useActions(logic) + + const hogFunctionsEnabled = !!useFeatureFlag('HOG_FUNCTIONS') + const { groupsTaxonomicTypes } = useValues(groupsModel) + + if (loading && !loaded) { + return + } + + if (!loaded) { + return + } + + if (!hogFunctionsEnabled && !id) { + return ( +
+
+

Feature not enabled

+

Hog functions are not enabled for you yet. If you think they should be, contact support.

+
+
+ ) + } + const buttons = ( + <> + resetForm()} + disabledReason={ + !configurationChanged ? 'No changes' : isConfigurationSubmitting ? 'Saving in progress…' : undefined + } + > + Clear changes + + + {templateId ? 'Create' : 'Save'} + + + ) + + return ( +
+ +
+
+
+
+
+ 🦔 +
+ Hog Function +
+ + + {({ value, onChange }) => ( + onChange(!value)} + checked={value} + disabled={loading} + bordered + /> + )} + +
+ + + + + + +
+ +
+ + {({ value, onChange }) => ( + <> + onChange({ ...value, filter_test_accounts: val })} + fullWidth + /> + { + onChange({ + ...payload, + filter_test_accounts: value?.filter_test_accounts, + }) + }} + typeKey="plugin-filters" + mathAvailability={MathAvailability.None} + hideRename + hideDuplicate + showNestedArrow={false} + actionsTaxonomicGroupTypes={[ + TaxonomicFilterGroupType.Events, + TaxonomicFilterGroupType.Actions, + ]} + propertiesTaxonomicGroupTypes={[ + TaxonomicFilterGroupType.EventProperties, + TaxonomicFilterGroupType.EventFeatureFlags, + TaxonomicFilterGroupType.Elements, + TaxonomicFilterGroupType.PersonProperties, + TaxonomicFilterGroupType.HogQLExpression, + ...groupsTaxonomicTypes, + ]} + propertyFiltersPopover + addFilterDefaultOptions={{ + id: '$pageview', + name: '$pageview', + type: EntityTypes.EVENTS, + }} + buttonCopy="Add event filter" + /> + + )} + + +

+ This destination will be triggered if any of the above filters match. +

+
+
+ +
+
+
+

Function configuration

+ + setShowSource(!showSource)}> + {showSource ? 'Hide source code' : 'Show source code'} + +
+ + {showSource ? ( +
+ + + + + + {({ value, onChange }) => ( + // TODO: Fix this so we don't have to click "update and run" + { + onChange(q.code) + }} + /> + )} + +
+ ) : ( +
+ {configuration?.inputs_schema?.length ? ( + configuration?.inputs_schema.map((schema) => { + return ( +
+ + {({ value, onChange }) => { + return ( + onChange({ value: val })} + /> + ) + }} + +
+ ) + }) + ) : ( + + This function does not require any input variables. + + )} +
+ )} +
+
{buttons}
+
+
+
+
+ ) +} diff --git a/frontend/src/scenes/pipeline/hogfunctions/pipelineHogFunctionConfigurationLogic.tsx b/frontend/src/scenes/pipeline/hogfunctions/pipelineHogFunctionConfigurationLogic.tsx new file mode 100644 index 00000000000000..8c90cb6e93334f --- /dev/null +++ b/frontend/src/scenes/pipeline/hogfunctions/pipelineHogFunctionConfigurationLogic.tsx @@ -0,0 +1,250 @@ +import { actions, afterMount, kea, key, listeners, path, props, reducers, selectors } from 'kea' +import { forms } from 'kea-forms' +import { loaders } from 'kea-loaders' +import { router } from 'kea-router' +import { subscriptions } from 'kea-subscriptions' +import api from 'lib/api' +import { urls } from 'scenes/urls' + +import { + FilterType, + HogFunctionTemplateType, + HogFunctionType, + PipelineNodeTab, + PipelineStage, + PluginConfigFilters, + PluginConfigTypeNew, +} from '~/types' + +import type { pipelineHogFunctionConfigurationLogicType } from './pipelineHogFunctionConfigurationLogicType' +import { HOG_FUNCTION_TEMPLATES } from './templates/hog-templates' + +export interface PipelineHogFunctionConfigurationLogicProps { + templateId?: string + id?: string +} + +function sanitizeFilters(filters?: FilterType): PluginConfigTypeNew['filters'] { + if (!filters) { + return null + } + const sanitized: PluginConfigFilters = {} + + if (filters.events) { + sanitized.events = filters.events.map((f) => ({ + id: f.id, + type: 'events', + name: f.name, + order: f.order, + properties: f.properties, + })) + } + + if (filters.actions) { + sanitized.actions = filters.actions.map((f) => ({ + id: f.id, + type: 'actions', + name: f.name, + order: f.order, + properties: f.properties, + })) + } + + if (filters.filter_test_accounts) { + sanitized.filter_test_accounts = filters.filter_test_accounts + } + + return Object.keys(sanitized).length > 0 ? sanitized : undefined +} + +// Should likely be somewhat similar to pipelineBatchExportConfigurationLogic +export const pipelineHogFunctionConfigurationLogic = kea([ + props({} as PipelineHogFunctionConfigurationLogicProps), + key(({ id, templateId }: PipelineHogFunctionConfigurationLogicProps) => { + return id ?? templateId ?? 'new' + }), + path((id) => ['scenes', 'pipeline', 'pipelineHogFunctionConfigurationLogic', id]), + actions({ + setShowSource: (showSource: boolean) => ({ showSource }), + resetForm: true, + }), + reducers({ + showSource: [ + false, + { + setShowSource: (_, { showSource }) => showSource, + }, + ], + }), + loaders(({ props }) => ({ + template: [ + null as HogFunctionTemplateType | null, + { + loadTemplate: async () => { + if (!props.templateId) { + return null + } + const res = HOG_FUNCTION_TEMPLATES.find((template) => template.id === props.templateId) + + if (!res) { + throw new Error('Template not found') + } + return res + }, + }, + ], + + hogFunction: [ + null as HogFunctionType | null, + { + loadHogFunction: async () => { + if (!props.id) { + return null + } + + return await api.hogFunctions.get(props.id) + }, + }, + ], + })), + forms(({ values, props, actions }) => ({ + configuration: { + defaults: {} as HogFunctionType, + alwaysShowErrors: true, + errors: (data) => { + return { + name: !data.name ? 'Name is required' : null, + ...values.inputFormErrors, + } + }, + submit: async (data) => { + const sanitizedInputs = {} + + data.inputs_schema?.forEach((input) => { + if (input.type === 'json' && typeof data.inputs[input.key].value === 'string') { + try { + sanitizedInputs[input.key] = { + value: JSON.parse(data.inputs[input.key].value), + } + } catch (e) { + // Ignore + } + } else { + sanitizedInputs[input.key] = { + value: data.inputs[input.key].value, + } + } + }) + + const payload = { + ...data, + filters: data.filters ? sanitizeFilters(data.filters) : null, + inputs: sanitizedInputs, + } + + try { + if (!props.id) { + return await api.hogFunctions.create(payload) + } + return await api.hogFunctions.update(props.id, payload) + } catch (e) { + const maybeValidationError = (e as any).data + if (maybeValidationError?.type === 'validation_error') { + if (maybeValidationError.attr.includes('inputs__')) { + actions.setConfigurationManualErrors({ + inputs: { + [maybeValidationError.attr.split('__')[1]]: maybeValidationError.detail, + }, + }) + } else { + actions.setConfigurationManualErrors({ + [maybeValidationError.attr]: maybeValidationError.detail, + }) + } + } + throw e + } + }, + }, + })), + selectors(() => ({ + loading: [ + (s) => [s.hogFunctionLoading, s.templateLoading], + (hogFunctionLoading, templateLoading) => hogFunctionLoading || templateLoading, + ], + loaded: [(s) => [s.hogFunction, s.template], (hogFunction, template) => !!hogFunction || !!template], + + inputFormErrors: [ + (s) => [s.configuration], + (configuration) => { + const inputs = configuration.inputs ?? {} + const inputErrors = {} + + configuration.inputs_schema?.forEach((input) => { + if (input.required && !inputs[input.key]) { + inputErrors[input.key] = 'This field is required' + } + + if (input.type === 'json' && typeof inputs[input.key] === 'string') { + try { + JSON.parse(inputs[input.key].value) + } catch (e) { + inputErrors[input.key] = 'Invalid JSON' + } + } + }) + + return Object.keys(inputErrors).length > 0 + ? { + inputs: inputErrors, + } + : null + }, + ], + })), + + listeners(({ actions, values, cache, props }) => ({ + loadTemplateSuccess: () => actions.resetForm(), + loadHogFunctionSuccess: () => actions.resetForm(), + resetForm: () => { + const savedValue = values.hogFunction ?? values.template + actions.resetConfiguration({ + ...savedValue, + inputs: (savedValue as any)?.inputs ?? {}, + ...(cache.configFromUrl || {}), + }) + }, + + submitConfigurationSuccess: ({ configuration }) => { + if (!props.id) { + router.actions.replace( + urls.pipelineNode( + PipelineStage.Destination, + `hog-${configuration.id}`, + PipelineNodeTab.Configuration + ) + ) + } + }, + })), + afterMount(({ props, actions, cache }) => { + if (props.templateId) { + cache.configFromUrl = router.values.hashParams.configuration + actions.loadTemplate() // comes with plugin info + } else if (props.id) { + actions.loadHogFunction() + } + }), + + subscriptions(({ props, cache }) => ({ + configuration: (configuration) => { + if (props.templateId) { + // Sync state to the URL bar if new + cache.ignoreUrlChange = true + router.actions.replace(router.values.location.pathname, undefined, { + configuration, + }) + } + }, + })), +]) diff --git a/frontend/src/scenes/pipeline/hogfunctions/templates/hog-templates.tsx b/frontend/src/scenes/pipeline/hogfunctions/templates/hog-templates.tsx new file mode 100644 index 00000000000000..cf76222fb16a0f --- /dev/null +++ b/frontend/src/scenes/pipeline/hogfunctions/templates/hog-templates.tsx @@ -0,0 +1,58 @@ +import { HogFunctionTemplateType } from '~/types' + +export const HOG_FUNCTION_TEMPLATES: HogFunctionTemplateType[] = [ + { + id: 'template-webhook', + name: 'HogHook', + description: 'Sends a webhook templated by the incoming event data', + hog: "fetch(inputs.url, {\n 'headers': inputs.headers,\n 'body': inputs.payload,\n 'method': inputs.method,\n 'payload': inputs.payload\n});", + inputs_schema: [ + { + key: 'url', + type: 'string', + label: 'Webhook URL', + secret: false, + required: true, + }, + { + key: 'method', + type: 'choice', + label: 'Method', + secret: false, + choices: [ + { + label: 'POST', + value: 'POST', + }, + { + label: 'PUT', + value: 'PUT', + }, + { + label: 'GET', + value: 'GET', + }, + { + label: 'DELETE', + value: 'DELETE', + }, + ], + required: false, + }, + { + key: 'payload', + type: 'json', + label: 'JSON Payload', + secret: false, + required: false, + }, + { + key: 'headers', + type: 'dictionary', + label: 'Headers', + secret: false, + required: false, + }, + ], + }, +] diff --git a/frontend/src/scenes/pipeline/pipelineNodeLogic.tsx b/frontend/src/scenes/pipeline/pipelineNodeLogic.tsx index 38d5acba5fcd3d..f9a4d7d66b824c 100644 --- a/frontend/src/scenes/pipeline/pipelineNodeLogic.tsx +++ b/frontend/src/scenes/pipeline/pipelineNodeLogic.tsx @@ -24,7 +24,11 @@ type BatchExportNodeId = { backend: PipelineBackend.BatchExport id: string } -export type PipelineNodeLimitedType = PluginNodeId | BatchExportNodeId +type HogFunctionNodeId = { + backend: PipelineBackend.HogFunction + id: string +} +export type PipelineNodeLimitedType = PluginNodeId | BatchExportNodeId | HogFunctionNodeId export const pipelineNodeLogic = kea([ props({} as PipelineNodeLogicProps), @@ -61,18 +65,23 @@ export const pipelineNodeLogic = kea([ }, ], ], + + nodeBackend: [ + (s) => [s.node], + (node): PipelineBackend => { + return node.backend + }, + ], node: [ (_, p) => [p.id], (id): PipelineNodeLimitedType => { return typeof id === 'string' - ? { backend: PipelineBackend.BatchExport, id: id } - : { backend: PipelineBackend.Plugin, id: id } + ? id.indexOf('hog-') === 0 + ? { backend: PipelineBackend.HogFunction, id: `${id}`.replace('hog-', '') } + : { backend: PipelineBackend.BatchExport, id } + : { backend: PipelineBackend.Plugin, id } }, ], - nodeBackend: [ - (_, p) => [p.id], - (id): PipelineBackend => (typeof id === 'string' ? PipelineBackend.BatchExport : PipelineBackend.Plugin), - ], tabs: [ (s) => [s.nodeBackend], (nodeBackend) => { diff --git a/frontend/src/scenes/pipeline/pipelineNodeNewLogic.tsx b/frontend/src/scenes/pipeline/pipelineNodeNewLogic.tsx index 395055b913a9b2..81e45ff15d394f 100644 --- a/frontend/src/scenes/pipeline/pipelineNodeNewLogic.tsx +++ b/frontend/src/scenes/pipeline/pipelineNodeNewLogic.tsx @@ -18,6 +18,7 @@ export interface PipelineNodeNewLogicProps { stage: PipelineStage | null pluginId: number | null batchExportDestination: string | null + hogFunctionId: string | null } export const pipelineNodeNewLogic = kea([ @@ -25,12 +26,7 @@ export const pipelineNodeNewLogic = kea([ connect({ values: [userLogic, ['user']], }), - path((pluginIdOrBatchExportDestination) => [ - 'scenes', - 'pipeline', - 'pipelineNodeNewLogic', - pluginIdOrBatchExportDestination, - ]), + path((id) => ['scenes', 'pipeline', 'pipelineNodeNewLogic', id]), actions({ createNewButtonPressed: (stage: PipelineStage, id: number | BatchExportService['type']) => ({ stage, id }), }), diff --git a/frontend/src/scenes/pipeline/types.ts b/frontend/src/scenes/pipeline/types.ts index dc6ac93442df9d..f958ebb887ca61 100644 --- a/frontend/src/scenes/pipeline/types.ts +++ b/frontend/src/scenes/pipeline/types.ts @@ -1,6 +1,7 @@ import { BatchExportConfiguration, BatchExportService, + HogFunctionType, PipelineStage, PluginConfigWithPluginInfoNew, PluginType, @@ -9,6 +10,7 @@ import { export enum PipelineBackend { BatchExport = 'batch_export', Plugin = 'plugin', + HogFunction = 'hog_function', } // Base - we're taking a discriminated union approach here, so that TypeScript can discern types for free @@ -39,6 +41,11 @@ export interface BatchExportBasedNode extends PipelineNodeBase { interval: BatchExportConfiguration['interval'] } +export interface HogFunctionBasedNode extends PipelineNodeBase { + backend: PipelineBackend.HogFunction + id: string +} + // Stage: Transformations export interface Transformation extends PluginBasedNode { @@ -55,7 +62,11 @@ export interface WebhookDestination extends PluginBasedNode { export interface BatchExportDestination extends BatchExportBasedNode { stage: PipelineStage.Destination } -export type Destination = BatchExportDestination | WebhookDestination +export interface FunctionDestination extends HogFunctionBasedNode { + stage: PipelineStage.Destination + interval: 'realtime' +} +export type Destination = BatchExportDestination | WebhookDestination | FunctionDestination export interface DataImportApp extends PluginBasedNode { stage: PipelineStage.DataImport @@ -84,7 +95,7 @@ function isPluginConfig( } export function convertToPipelineNode( - candidate: PluginConfigWithPluginInfoNew | BatchExportConfiguration, + candidate: PluginConfigWithPluginInfoNew | BatchExportConfiguration | HogFunctionType, stage: S ): S extends PipelineStage.Transformation ? Transformation @@ -98,7 +109,20 @@ export function convertToPipelineNode( ? ImportApp : never { let node: PipelineNode - if (isPluginConfig(candidate)) { + // check if type is a hog function + if ('hog' in candidate) { + node = { + stage: stage as PipelineStage.Destination, + backend: PipelineBackend.HogFunction, + interval: 'realtime', + id: `hog-${candidate.id}`, + name: candidate.name, + description: candidate.description, + enabled: candidate.enabled, + created_at: candidate.created_at, + updated_at: candidate.created_at, + } + } else if (isPluginConfig(candidate)) { const almostNode: Omit< Transformation | WebhookDestination | SiteApp | ImportApp | DataImportApp, 'frequency' | 'order' diff --git a/frontend/src/scenes/scenes.ts b/frontend/src/scenes/scenes.ts index 94983524158f6f..3fc5293e0beda4 100644 --- a/frontend/src/scenes/scenes.ts +++ b/frontend/src/scenes/scenes.ts @@ -529,7 +529,7 @@ export const routes: Record = { [urls.persons()]: Scene.PersonsManagement, [urls.pipelineNodeDataWarehouseNew()]: Scene.pipelineNodeDataWarehouseNew, [urls.pipelineNodeNew(':stage')]: Scene.PipelineNodeNew, - [urls.pipelineNodeNew(':stage', ':pluginIdOrBatchExportDestination')]: Scene.PipelineNodeNew, + [urls.pipelineNodeNew(':stage', ':id')]: Scene.PipelineNodeNew, [urls.pipeline(':tab')]: Scene.Pipeline, [urls.pipelineNode(':stage', ':id', ':nodeTab')]: Scene.PipelineNode, [urls.groups(':groupTypeIndex')]: Scene.PersonsManagement, diff --git a/frontend/src/scenes/urls.ts b/frontend/src/scenes/urls.ts index cc70cd9fc7f432..a06f49aeeb5b60 100644 --- a/frontend/src/scenes/urls.ts +++ b/frontend/src/scenes/urls.ts @@ -120,13 +120,13 @@ export const urls = { encode ? `/persons/${encodeURIComponent(uuid)}` : `/persons/${uuid}`, persons: (): string => '/persons', pipelineNodeDataWarehouseNew: (): string => `/pipeline/new/data-warehouse`, - pipelineNodeNew: (stage: PipelineStage | ':stage', pluginIdOrBatchExportDestination?: string | number): string => { + pipelineNodeNew: (stage: PipelineStage | ':stage', id?: string | number): string => { if (stage === PipelineStage.DataImport) { // should match 'pipelineNodeDataWarehouseNew' return `/pipeline/new/data-warehouse` } - return `/pipeline/new/${stage}${pluginIdOrBatchExportDestination ? `/${pluginIdOrBatchExportDestination}` : ''}` + return `/pipeline/new/${stage}${id ? `/${id}` : ''}` }, pipeline: (tab?: PipelineTab | ':tab'): string => `/pipeline/${tab ? tab : PipelineTab.Overview}`, /** @param id 'new' for new, uuid for batch exports and numbers for plugins */ diff --git a/frontend/src/types.ts b/frontend/src/types.ts index c6f41edc477164..2d6a2ee8fbf6a8 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -4100,6 +4100,44 @@ export type OnboardingProduct = { scene: Scene } +export type HogFunctionInputSchemaType = { + type: 'string' | 'boolean' | 'dictionary' | 'choice' | 'json' + key: string + label: string + choices?: { value: string; label: string }[] + required?: boolean + default?: any + secret?: boolean + description?: string +} + +export type HogFunctionType = { + id: string + name: string + description: string + created_by: UserBasicType | null + created_at: string + updated_at: string + enabled: boolean + hog: string + + inputs_schema: HogFunctionInputSchemaType[] + inputs: Record< + string, + { + value: any + bytecode?: any + } + > + filters?: PluginConfigFilters | null + template?: HogFunctionTemplateType +} + +export type HogFunctionTemplateType = Pick< + HogFunctionType, + 'id' | 'name' | 'description' | 'hog' | 'inputs_schema' | 'filters' +> + export interface AnomalyCondition { absoluteThreshold: { lower?: number diff --git a/hogvm/typescript/package.json b/hogvm/typescript/package.json index b7977c949edad4..3080a86f544f9c 100644 --- a/hogvm/typescript/package.json +++ b/hogvm/typescript/package.json @@ -1,9 +1,9 @@ { "name": "@posthog/hogvm", - "version": "1.0.10", - "description": "PostHog HogQL Virtual Machine", - "types": "dist/execute.d.ts", - "main": "dist/execute.js", + "version": "1.0.11", + "description": "PostHog Hog Virtual Machine", + "types": "dist/index.d.ts", + "main": "dist/index.js", "packageManager": "pnpm@8.3.1", "scripts": { "test": "jest --runInBand --forceExit", diff --git a/hogvm/typescript/src/execute.ts b/hogvm/typescript/src/execute.ts index 0bb1c0b5c81d5e..4101d64f69d1b0 100644 --- a/hogvm/typescript/src/execute.ts +++ b/hogvm/typescript/src/execute.ts @@ -1,6 +1,6 @@ import { Operation } from './operation' import { ASYNC_STL, STL } from './stl/stl' -import { convertJSToHog, getNestedValue, like, setNestedValue } from './utils' +import { convertHogToJS, convertJSToHog, getNestedValue, like, setNestedValue } from './utils' const DEFAULT_MAX_ASYNC_STEPS = 100 const DEFAULT_TIMEOUT = 5 // seconds @@ -58,8 +58,10 @@ export async function execAsync(bytecode: any[], options?: ExecOptions): Promise if (response.state && response.asyncFunctionName && response.asyncFunctionArgs) { vmState = response.state if (options?.asyncFunctions && response.asyncFunctionName in options.asyncFunctions) { - const result = await options?.asyncFunctions[response.asyncFunctionName](...response.asyncFunctionArgs) - vmState.stack.push(result) + const result = await options?.asyncFunctions[response.asyncFunctionName]( + ...response.asyncFunctionArgs.map(convertHogToJS) + ) + vmState.stack.push(convertJSToHog(result)) } else if (response.asyncFunctionName in ASYNC_STL) { const result = await ASYNC_STL[response.asyncFunctionName]( response.asyncFunctionArgs, @@ -333,7 +335,7 @@ export function exec(code: any[] | VMState, options?: ExecOptions): ExecResult { .fill(null) .map(() => popStack()) if (options?.functions && options.functions[name] && name !== 'toString') { - stack.push(options.functions[name](...args)) + stack.push(convertJSToHog(options.functions[name](...args.map(convertHogToJS)))) } else if ( name !== 'toString' && ((options?.asyncFunctions && options.asyncFunctions[name]) || name in ASYNC_STL) diff --git a/hogvm/typescript/src/index.ts b/hogvm/typescript/src/index.ts new file mode 100644 index 00000000000000..20f547aef5f9fd --- /dev/null +++ b/hogvm/typescript/src/index.ts @@ -0,0 +1,3 @@ +export * from './execute' +export * from './operation' +export * from './utils' diff --git a/latest_migrations.manifest b/latest_migrations.manifest index 32da32018dacda..37040b220d4821 100644 --- a/latest_migrations.manifest +++ b/latest_migrations.manifest @@ -5,7 +5,7 @@ contenttypes: 0002_remove_content_type_name ee: 0016_rolemembership_organization_member otp_static: 0002_throttling otp_totp: 0002_auto_20190420_0723 -posthog: 0424_survey_current_iteration_and_more +posthog: 0425_hogfunction sessions: 0001_initial social_django: 0010_uid_db_index two_factor: 0007_auto_20201201_1019 diff --git a/plugin-server/package.json b/plugin-server/package.json index e5766fa3d44f5e..3344291ce0d1dc 100644 --- a/plugin-server/package.json +++ b/plugin-server/package.json @@ -50,6 +50,7 @@ "@google-cloud/storage": "^5.8.5", "@maxmind/geoip2-node": "^3.4.0", "@posthog/clickhouse": "^1.7.0", + "@posthog/hogvm": "^1.0.11", "@posthog/plugin-scaffold": "1.4.4", "@sentry/node": "^7.49.0", "@sentry/profiling-node": "^0.3.0", diff --git a/plugin-server/pnpm-lock.yaml b/plugin-server/pnpm-lock.yaml index 05fb5a15d84bd4..af85a11df6436b 100644 --- a/plugin-server/pnpm-lock.yaml +++ b/plugin-server/pnpm-lock.yaml @@ -43,6 +43,9 @@ dependencies: '@posthog/clickhouse': specifier: ^1.7.0 version: 1.7.0 + '@posthog/hogvm': + specifier: ^1.0.11 + version: 1.0.11 '@posthog/plugin-scaffold': specifier: 1.4.4 version: 1.4.4 @@ -3104,6 +3107,10 @@ packages: engines: {node: '>=12'} dev: false + /@posthog/hogvm@1.0.11: + resolution: {integrity: sha512-W1m4UPmpaNwm9+Rwpb3rjuZd3z+/gO9MsxibCnxdTndrFgIrNjGOas2ZEpZqJblV3sgubFbGq6IXdORbM+nv5w==} + dev: false + /@posthog/plugin-scaffold@1.4.4: resolution: {integrity: sha512-3z1ENm1Ys5lEQil0H7TVOqHvD24+ydiZFk5hggpbHRx1iOxAK+Eu5qFyAROwPUcCo7NOYjmH2xL1C4B1vaHilg==} dependencies: diff --git a/plugin-server/src/capabilities.ts b/plugin-server/src/capabilities.ts index cda8da5b20abdf..47d30482bd72dc 100644 --- a/plugin-server/src/capabilities.ts +++ b/plugin-server/src/capabilities.ts @@ -23,6 +23,7 @@ export function getPluginServerCapabilities(config: PluginsServerConfig): Plugin personOverrides: true, appManagementSingleton: true, preflightSchedules: true, + cdpProcessedEvents: true, ...sharedCapabilities, } case PluginServerMode.ingestion: @@ -87,5 +88,11 @@ export function getPluginServerCapabilities(config: PluginsServerConfig): Plugin personOverrides: true, ...sharedCapabilities, } + + case PluginServerMode.cdp_processed_events: + return { + cdpProcessedEvents: true, + ...sharedCapabilities, + } } } diff --git a/plugin-server/src/cdp/cdp-processed-events-consumer.ts b/plugin-server/src/cdp/cdp-processed-events-consumer.ts new file mode 100644 index 00000000000000..300b592334686c --- /dev/null +++ b/plugin-server/src/cdp/cdp-processed-events-consumer.ts @@ -0,0 +1,258 @@ +import { features, librdkafkaVersion, Message } from 'node-rdkafka' +import { Histogram } from 'prom-client' + +import { KAFKA_EVENTS_JSON } from '../config/kafka-topics' +import { BatchConsumer, startBatchConsumer } from '../kafka/batch-consumer' +import { createRdConnectionConfigFromEnvVars, createRdProducerConfigFromEnvVars } from '../kafka/config' +import { createKafkaProducer } from '../kafka/producer' +import { addSentryBreadcrumbsEventListeners } from '../main/ingestion-queues/kafka-metrics' +import { runInstrumentedFunction } from '../main/utils' +import { GroupTypeToColumnIndex, Hub, PluginsServerConfig, RawClickHouseEvent, TeamId } from '../types' +import { KafkaProducerWrapper } from '../utils/db/kafka-producer-wrapper' +import { PostgresRouter } from '../utils/db/postgres' +import { status } from '../utils/status' +import { AppMetrics } from '../worker/ingestion/app-metrics' +import { GroupTypeManager } from '../worker/ingestion/group-type-manager' +import { OrganizationManager } from '../worker/ingestion/organization-manager' +import { TeamManager } from '../worker/ingestion/team-manager' +import { RustyHook } from '../worker/rusty-hook' +import { HogExecutor } from './hog-executor' +import { HogFunctionManager } from './hog-function-manager' +import { HogFunctionInvocation, HogFunctionInvocationResult } from './types' +import { convertToHogFunctionInvocationGlobals } from './utils' + +// Must require as `tsc` strips unused `import` statements and just requiring this seems to init some globals +require('@sentry/tracing') + +// WARNING: Do not change this - it will essentially reset the consumer +const KAFKA_CONSUMER_GROUP_ID = 'cdp-function-executor' +const KAFKA_CONSUMER_SESSION_TIMEOUT_MS = 90_000 +const BUCKETS_KB_WRITTEN = [0, 128, 512, 1024, 5120, 10240, 20480, 51200, 102400, 204800, Infinity] + +const histogramKafkaBatchSize = new Histogram({ + name: 'cdp_function_executor_batch_size', + help: 'The size of the batches we are receiving from Kafka', + buckets: [0, 50, 100, 250, 500, 750, 1000, 1500, 2000, 3000, Infinity], +}) + +const histogramKafkaBatchSizeKb = new Histogram({ + name: 'cdp_function_executor_batch_size_kb', + help: 'The size in kb of the batches we are receiving from Kafka', + buckets: BUCKETS_KB_WRITTEN, +}) + +export interface TeamIDWithConfig { + teamId: TeamId | null + consoleLogIngestionEnabled: boolean +} + +export class CdpProcessedEventsConsumer { + batchConsumer?: BatchConsumer + teamManager: TeamManager + organizationManager: OrganizationManager + groupTypeManager: GroupTypeManager + hogFunctionManager: HogFunctionManager + hogExecutor?: HogExecutor + appMetrics?: AppMetrics + topic: string + consumerGroupId: string + isStopping = false + + private kafkaProducer?: KafkaProducerWrapper + + private promises: Set> = new Set() + + constructor(private config: PluginsServerConfig, private hub?: Hub) { + this.topic = KAFKA_EVENTS_JSON + this.consumerGroupId = KAFKA_CONSUMER_GROUP_ID + + const postgres = hub?.postgres ?? new PostgresRouter(config) + + this.teamManager = new TeamManager(postgres, config) + this.organizationManager = new OrganizationManager(postgres, this.teamManager) + this.groupTypeManager = new GroupTypeManager(postgres, this.teamManager) + this.hogFunctionManager = new HogFunctionManager(postgres, config) + } + + private scheduleWork(promise: Promise): Promise { + this.promises.add(promise) + void promise.finally(() => this.promises.delete(promise)) + return promise + } + + public async consume(invocation: HogFunctionInvocation): Promise { + return await this.hogExecutor!.executeMatchingFunctions(invocation) + } + + public async handleEachBatch(messages: Message[], heartbeat: () => void): Promise { + status.info('🔁', `cdp-function-executor - handling batch`, { + size: messages.length, + }) + await runInstrumentedFunction({ + statsKey: `cdpFunctionExecutor.handleEachBatch`, + sendTimeoutGuardToSentry: false, + func: async () => { + histogramKafkaBatchSize.observe(messages.length) + histogramKafkaBatchSizeKb.observe(messages.reduce((acc, m) => (m.value?.length ?? 0) + acc, 0) / 1024) + + const invocations: HogFunctionInvocation[] = [] + + await runInstrumentedFunction({ + statsKey: `cdpFunctionExecutor.handleEachBatch.parseKafkaMessages`, + func: async () => { + // TODO: Early exit for events without associated hooks + + await Promise.all( + messages.map(async (message) => { + try { + const clickHouseEvent = JSON.parse(message.value!.toString()) as RawClickHouseEvent + + let groupTypes: GroupTypeToColumnIndex | undefined = undefined + + if ( + await this.organizationManager.hasAvailableFeature( + clickHouseEvent.team_id, + 'group_analytics' + ) + ) { + // If the organization has group analytics enabled then we enrich the event with group data + groupTypes = await this.groupTypeManager.fetchGroupTypes( + clickHouseEvent.team_id + ) + } + + // TODO: Clean up all of this and parallelise + // TODO: We can fetch alot of teams and things in parallel + + const team = await this.teamManager.fetchTeam(clickHouseEvent.team_id) + if (!team) { + return + } + const globals = convertToHogFunctionInvocationGlobals( + clickHouseEvent, + team, + this.config.SITE_URL ?? 'http://localhost:8000', + groupTypes + ) + + invocations.push({ + globals, + }) + } catch (e) { + status.error('Error parsing message', e) + } + }) + ) + }, + }) + heartbeat() + + const invocationResults: HogFunctionInvocationResult[] = [] + + await runInstrumentedFunction({ + statsKey: `cdpFunctionExecutor.handleEachBatch.consumeBatch`, + func: async () => { + // TODO: Parallelise this + for (const message of invocations) { + const results = await this.consume(message) + invocationResults.push(...results) + heartbeat() + } + }, + }) + + // TODO: Follow up - process metrics from the invocationResults + // await runInstrumentedFunction({ + // statsKey: `cdpFunctionExecutor.handleEachBatch.queueMetrics`, + // func: async () => { + // // TODO: + // }, + // }) + }, + }) + } + + public async start(): Promise { + status.info('🔁', 'cdp-function-executor - starting', { + librdKafkaVersion: librdkafkaVersion, + kafkaCapabilities: features, + }) + + // NOTE: This is the only place where we need to use the shared server config + const globalConnectionConfig = createRdConnectionConfigFromEnvVars(this.config) + const globalProducerConfig = createRdProducerConfigFromEnvVars(this.config) + + await this.hogFunctionManager.start() + + this.kafkaProducer = new KafkaProducerWrapper( + await createKafkaProducer(globalConnectionConfig, globalProducerConfig) + ) + + const rustyHook = this.hub?.rustyHook ?? new RustyHook(this.config) + this.appMetrics = + this.hub?.appMetrics ?? + new AppMetrics( + this.kafkaProducer, + this.config.APP_METRICS_FLUSH_FREQUENCY_MS, + this.config.APP_METRICS_FLUSH_MAX_QUEUE_SIZE + ) + this.hogExecutor = new HogExecutor(this.config, this.hogFunctionManager, rustyHook) + this.kafkaProducer.producer.connect() + + this.batchConsumer = await startBatchConsumer({ + connectionConfig: createRdConnectionConfigFromEnvVars(this.config), + groupId: this.consumerGroupId, + topic: this.topic, + autoCommit: true, + sessionTimeout: KAFKA_CONSUMER_SESSION_TIMEOUT_MS, + maxPollIntervalMs: this.config.KAFKA_CONSUMPTION_MAX_POLL_INTERVAL_MS, + // the largest size of a message that can be fetched by the consumer. + // the largest size our MSK cluster allows is 20MB + // we only use 9 or 10MB but there's no reason to limit this 🤷️ + consumerMaxBytes: this.config.KAFKA_CONSUMPTION_MAX_BYTES, + consumerMaxBytesPerPartition: this.config.KAFKA_CONSUMPTION_MAX_BYTES_PER_PARTITION, + // our messages are very big, so we don't want to buffer too many + // queuedMinMessages: this.config.KAFKA_QUEUE_SIZE, + consumerMaxWaitMs: this.config.KAFKA_CONSUMPTION_MAX_WAIT_MS, + consumerErrorBackoffMs: this.config.KAFKA_CONSUMPTION_ERROR_BACKOFF_MS, + fetchBatchSize: this.config.INGESTION_BATCH_SIZE, + batchingTimeoutMs: this.config.KAFKA_CONSUMPTION_BATCHING_TIMEOUT_MS, + topicCreationTimeoutMs: this.config.KAFKA_TOPIC_CREATION_TIMEOUT_MS, + eachBatch: async (messages, { heartbeat }) => { + return await this.scheduleWork(this.handleEachBatch(messages, heartbeat)) + }, + callEachBatchWhenEmpty: false, + }) + + addSentryBreadcrumbsEventListeners(this.batchConsumer.consumer) + + this.batchConsumer.consumer.on('disconnected', async (err) => { + // since we can't be guaranteed that the consumer will be stopped before some other code calls disconnect + // we need to listen to disconnect and make sure we're stopped + status.info('🔁', 'cdp-function-executor batch consumer disconnected, cleaning up', { err }) + await this.stop() + }) + } + + public async stop(): Promise[]> { + status.info('🔁', 'cdp-function-executor - stopping') + this.isStopping = true + + // Mark as stopping so that we don't actually process any more incoming messages, but still keep the process alive + await this.batchConsumer?.stop() + + const promiseResults = await Promise.allSettled(this.promises) + + await this.kafkaProducer?.disconnect() + await this.hogFunctionManager.stop() + + status.info('👍', 'cdp-function-executor - stopped!') + + return promiseResults + } + + public isHealthy() { + // TODO: Maybe extend this to check if we are shutting down so we don't get killed early. + return this.batchConsumer?.isHealthy() + } +} diff --git a/plugin-server/src/cdp/hog-executor.ts b/plugin-server/src/cdp/hog-executor.ts new file mode 100644 index 00000000000000..64eebb0e13be57 --- /dev/null +++ b/plugin-server/src/cdp/hog-executor.ts @@ -0,0 +1,299 @@ +import { convertHogToJS, convertJSToHog, exec, ExecResult, VMState } from '@posthog/hogvm' +import { Webhook } from '@posthog/plugin-scaffold' +import { PluginsServerConfig } from 'types' + +import { trackedFetch } from '../utils/fetch' +import { status } from '../utils/status' +import { RustyHook } from '../worker/rusty-hook' +import { HogFunctionManager } from './hog-function-manager' +import { + HogFunctionInvocation, + HogFunctionInvocationAsyncResponse, + HogFunctionInvocationGlobals, + HogFunctionInvocationResult, + HogFunctionType, +} from './types' +import { convertToHogFunctionFilterGlobal } from './utils' + +export const formatInput = (bytecode: any, globals: HogFunctionInvocation['globals']): any => { + // Similar to how we generate the bytecode by iterating over the values, + // here we iterate over the object and replace the bytecode with the actual values + // bytecode is indicated as an array beginning with ["_h"] + + if (Array.isArray(bytecode) && bytecode[0] === '_h') { + const res = exec(bytecode, { + globals, + timeout: 100, + maxAsyncSteps: 0, + }) + + if (!res.finished) { + // NOT ALLOWED + throw new Error('Input fields must be simple sync values') + } + return convertHogToJS(res.result) + } + + if (Array.isArray(bytecode)) { + return bytecode.map((item) => formatInput(item, globals)) + } else if (typeof bytecode === 'object') { + return Object.fromEntries(Object.entries(bytecode).map(([key, value]) => [key, formatInput(value, globals)])) + } else { + return bytecode + } +} + +export class HogExecutor { + constructor( + private serverConfig: PluginsServerConfig, + private hogFunctionManager: HogFunctionManager, + private rustyHook: RustyHook + ) {} + + /** + * Intended to be invoked as a starting point from an event + */ + async executeMatchingFunctions(invocation: HogFunctionInvocation): Promise { + let functions = this.hogFunctionManager.getTeamHogFunctions(invocation.globals.project.id) + + const filtersGlobals = convertToHogFunctionFilterGlobal(invocation.globals) + + // Filter all functions based on the invocation + functions = Object.fromEntries( + Object.entries(functions).filter(([_key, value]) => { + try { + const filters = value.filters + + if (!filters?.bytecode) { + // NOTE: If we don't have bytecode this indicates something went wrong. + // The model will always save a bytecode if it was compiled correctly + return false + } + + const filterResult = exec(filters.bytecode, { + globals: filtersGlobals, + timeout: 100, + maxAsyncSteps: 0, + }) + + if (typeof filterResult.result !== 'boolean') { + // NOTE: If the result is not a boolean we should not execute the function + return false + } + + return filterResult.result + } catch (error) { + status.error('🦔', `[HogExecutor] Error filtering function`, { + hogFunctionId: value.id, + hogFunctionName: value.name, + error: error.message, + }) + } + + return false + }) + ) + + if (!Object.keys(functions).length) { + return [] + } + + const results: HogFunctionInvocationResult[] = [] + + for (const hogFunction of Object.values(functions)) { + // Add the source of the trigger to the globals + const modifiedGlobals: HogFunctionInvocationGlobals = { + ...invocation.globals, + source: { + name: hogFunction.name ?? `Hog function: ${hogFunction.id}`, + url: `${invocation.globals.project.url}/pipeline/destinations/hog-${hogFunction.id}/configuration/`, + }, + } + + const result = await this.execute(hogFunction, { + ...invocation, + globals: modifiedGlobals, + }) + + results.push(result) + } + + return results + } + + /** + * Intended to be invoked as a continuation from an async function + */ + async executeAsyncResponse(invocation: HogFunctionInvocationAsyncResponse): Promise { + if (!invocation.hogFunctionId) { + throw new Error('No hog function id provided') + } + + const hogFunction = this.hogFunctionManager.getTeamHogFunctions(invocation.globals.project.id)[ + invocation.hogFunctionId + ] + + invocation.vmState.stack.push(convertJSToHog(invocation.response)) + + await this.execute(hogFunction, invocation, invocation.vmState) + } + + async execute( + hogFunction: HogFunctionType, + invocation: HogFunctionInvocation, + state?: VMState + ): Promise { + const loggingContext = { + hogFunctionId: hogFunction.id, + hogFunctionName: hogFunction.name, + hogFunctionUrl: invocation.globals.source?.url, + } + + status.info('🦔', `[HogExecutor] Executing function`, loggingContext) + + let error: any = null + + try { + const globals = this.buildHogFunctionGlobals(hogFunction, invocation) + + const res = exec(state ?? hogFunction.bytecode, { + globals, + timeout: 100, // NOTE: This will likely be configurable in the future + maxAsyncSteps: 5, // NOTE: This will likely be configurable in the future + asyncFunctions: { + // We need to pass these in but they don't actually do anything as it is a sync exec + fetch: async () => Promise.resolve(), + }, + }) + + console.log('🦔', `[HogExecutor] TESTING`, { + asyncFunctionArgs: res.asyncFunctionArgs, + asyncFunctionName: res.asyncFunctionName, + globals: globals, + }) + + if (!res.finished) { + status.info('🦔', `[HogExecutor] Function returned not finished. Executing async function`, { + ...loggingContext, + asyncFunctionName: res.asyncFunctionName, + }) + switch (res.asyncFunctionName) { + case 'fetch': + await this.asyncFunctionFetch(hogFunction, invocation, res) + break + default: + status.error( + '🦔', + `[HogExecutor] Unknown async function: ${res.asyncFunctionName}`, + loggingContext + ) + // TODO: Log error somewhere + } + } + // await this.appMetrics.queueMetric({ + // teamId: hogFunction.team_id, + // appId: hogFunction.id, // Add this as a generic string ID + // category: 'hogFunction', // TODO: Figure this out + // successes: 1, + // }) + } catch (err) { + error = err + + // await this.appMetrics.queueError( + // { + // teamId: hogFunction.team_id, + // appId: hogFunction.id, // Add this as a generic string ID + // category: 'hogFunction', + // failures: 1, + // }, + // { + // error, + // event, + // } + // ) + status.error('🦔', `[HogExecutor] Error executing function ${hogFunction.id} - ${hogFunction.name}`, error) + } + + return { + ...invocation, + success: !error, + error, + logs: [], // TODO: Add logs + } + } + + buildHogFunctionGlobals(hogFunction: HogFunctionType, invocation: HogFunctionInvocation): Record { + const builtInputs: Record = {} + + Object.entries(hogFunction.inputs).forEach(([key, item]) => { + // TODO: Replace this with iterator + builtInputs[key] = item.value + + if (item.bytecode) { + // Use the bytecode to compile the field + builtInputs[key] = formatInput(item.bytecode, invocation.globals) + } + }) + + return { + ...invocation.globals, + inputs: builtInputs, + } + } + + private async asyncFunctionFetch( + hogFunction: HogFunctionType, + invocation: HogFunctionInvocation, + execResult: ExecResult + ): Promise { + // TODO: validate the args + const args = (execResult.asyncFunctionArgs ?? []).map((arg) => convertHogToJS(arg)) + const url: string = args[0] + const options = args[1] + + const method = options.method || 'POST' + const headers = options.headers || { + 'Content-Type': 'application/json', + } + const body = options.body || {} + + const webhook: Webhook = { + url, + method: method, + headers: headers, + body: typeof body === 'string' ? body : JSON.stringify(body, undefined, 4), + } + + // NOTE: Purposefully disabled for now - once we have callback support we can re-enable + // const SPECIAL_CONFIG_ID = -3 // Hardcoded to mean Hog + // const success = await this.rustyHook.enqueueIfEnabledForTeam({ + // webhook: webhook, + // teamId: hogFunction.team_id, + // pluginId: SPECIAL_CONFIG_ID, + // pluginConfigId: SPECIAL_CONFIG_ID, + // }) + + const success = false + + // TODO: Temporary test code + if (!success) { + status.info('🦔', `[HogExecutor] Webhook not sent via rustyhook, sending directly instead`) + const fetchResponse = await trackedFetch(url, { + method: webhook.method, + body: webhook.body, + headers: webhook.headers, + timeout: this.serverConfig.EXTERNAL_REQUEST_TIMEOUT_MS, + }) + + await this.executeAsyncResponse({ + ...invocation, + hogFunctionId: hogFunction.id, + vmState: execResult.state!, + response: { + status: fetchResponse.status, + body: await fetchResponse.text(), + }, + }) + } + } +} diff --git a/plugin-server/src/cdp/hog-function-manager.ts b/plugin-server/src/cdp/hog-function-manager.ts new file mode 100644 index 00000000000000..53a1dc5da90a3d --- /dev/null +++ b/plugin-server/src/cdp/hog-function-manager.ts @@ -0,0 +1,132 @@ +import * as schedule from 'node-schedule' + +import { PluginsServerConfig, Team } from '../types' +import { PostgresRouter, PostgresUse } from '../utils/db/postgres' +import { PubSub } from '../utils/pubsub' +import { status } from '../utils/status' +import { HogFunctionType } from './types' + +export type HogFunctionMap = Record +export type HogFunctionCache = Record + +export class HogFunctionManager { + private started: boolean + private ready: boolean + private cache: HogFunctionCache + private pubSub: PubSub + private refreshJob?: schedule.Job + + constructor(private postgres: PostgresRouter, private serverConfig: PluginsServerConfig) { + this.started = false + this.ready = false + this.cache = {} + + this.pubSub = new PubSub(this.serverConfig, { + 'reload-hog-function': async (message) => { + const { hogFunctionId, teamId } = JSON.parse(message) + // TODO: Change to only if already loaded + + if (this.cache[teamId]?.[hogFunctionId]) { + await this.reloadHogFunction(teamId, hogFunctionId) + } + }, + }) + } + + public async start(): Promise { + // TRICKY - when running with individual capabilities, this won't run twice but locally or as a complete service it will... + if (this.started) { + return + } + this.started = true + await this.pubSub.start() + await this.reloadAllHogFunctions() + + // every 5 minutes all HogFunctionManager caches are reloaded for eventual consistency + this.refreshJob = schedule.scheduleJob('*/5 * * * *', async () => { + await this.reloadAllHogFunctions().catch((error) => { + status.error('🍿', 'Error reloading hog functions:', error) + }) + }) + this.ready = true + } + + public async stop(): Promise { + if (this.refreshJob) { + schedule.cancelJob(this.refreshJob) + } + + await this.pubSub.stop() + } + + public getTeamHogFunctions(teamId: Team['id']): HogFunctionMap { + if (!this.ready) { + throw new Error('HogFunctionManager is not ready! Run HogFunctionManager.start() before this') + } + return this.cache[teamId] || {} + } + + public async reloadAllHogFunctions(): Promise { + this.cache = await fetchAllHogFunctionsGroupedByTeam(this.postgres) + status.info('🍿', 'Fetched all hog functions from DB anew') + } + + public async reloadHogFunction(teamId: Team['id'], id: HogFunctionType['id']): Promise { + const item = await fetchHogFunction(this.postgres, id) + + if (item) { + this.cache[teamId][id] = item + } else { + delete this.cache[teamId][id] + } + } +} + +const HOG_FUNCTION_FIELDS = ['id', 'team_id', 'name', 'enabled', 'inputs', 'filters', 'bytecode'] + +export async function fetchAllHogFunctionsGroupedByTeam(client: PostgresRouter): Promise { + const items = ( + await client.query( + PostgresUse.COMMON_READ, + ` + SELECT ${HOG_FUNCTION_FIELDS.join(', ')} + FROM posthog_hogfunction + WHERE deleted = FALSE AND enabled = TRUE + `, + [], + 'fetchAllHogFunctions' + ) + ).rows + + const cache: HogFunctionCache = {} + for (const item of items) { + if (!cache[item.team_id]) { + cache[item.team_id] = {} + } + + cache[item.team_id][item.id] = item + } + + return cache +} + +export async function fetchHogFunction( + client: PostgresRouter, + id: HogFunctionType['id'] +): Promise { + const items: HogFunctionType[] = ( + await client.query( + PostgresUse.COMMON_READ, + `SELECT ${HOG_FUNCTION_FIELDS.join(', ')} + FROM posthog_hogfunction + WHERE id = $1 AND deleted = FALSE AND enabled = TRUE`, + [id], + 'fetchHogFunction' + ) + ).rows + if (!items.length) { + return null + } + + return items[0] +} diff --git a/plugin-server/src/cdp/types.ts b/plugin-server/src/cdp/types.ts new file mode 100644 index 00000000000000..c0910e36282620 --- /dev/null +++ b/plugin-server/src/cdp/types.ts @@ -0,0 +1,147 @@ +import { VMState } from '@posthog/hogvm' + +import { ElementPropertyFilter, EventPropertyFilter, PersonPropertyFilter } from '../types' + +export type HogBytecode = any[] + +// subset of EntityFilter +export interface HogFunctionFilterBase { + id: string + name: string | null + order: number + properties: (EventPropertyFilter | PersonPropertyFilter | ElementPropertyFilter)[] +} + +export interface HogFunctionFilterEvent extends HogFunctionFilterBase { + type: 'events' + bytecode: HogBytecode +} + +export interface HogFunctionFilterAction extends HogFunctionFilterBase { + type: 'actions' + // Loaded at run time from Action model + bytecode?: HogBytecode +} + +export type HogFunctionFilter = HogFunctionFilterEvent | HogFunctionFilterAction + +export interface HogFunctionFilters { + events?: HogFunctionFilterEvent[] + actions?: HogFunctionFilterAction[] + filter_test_accounts?: boolean + // Loaded at run time from Team model + filter_test_accounts_bytecode?: boolean + bytecode?: HogBytecode +} + +export type HogFunctionInvocationGlobals = { + project: { + id: number + name: string + url: string + } + source?: { + name: string + url: string + } + event: { + uuid: string + name: string + distinct_id: string + properties: Record + timestamp: string + url: string + } + person?: { + uuid: string + properties: Record + url: string + } + groups?: Record< + string, + { + id: string // the "key" of the group + type: string + index: number + url: string + properties: Record + } + > +} + +export type HogFunctionFilterGlobals = { + // Filter Hog is built in the same way as analytics so the global object is meant to be an event + event: string + timestamp: string + elements_chain: string + properties: Record + + person?: { + properties: Record + } + + group_0?: { + properties: Record + } + group_1?: { + properties: Record + } + group_2?: { + properties: Record + } + group_3?: { + properties: Record + } + group_4?: { + properties: Record + } +} + +export type HogFunctionInvocation = { + globals: HogFunctionInvocationGlobals +} + +export type HogFunctionInvocationResult = HogFunctionInvocation & { + success: boolean + error?: any + logs: string[] +} + +export type HogFunctionInvocationAsyncRequest = HogFunctionInvocation & { + hogFunctionId: HogFunctionType['id'] + vmState: VMState +} + +export type HogFunctionInvocationAsyncResponse = HogFunctionInvocationAsyncRequest & { + response: any +} + +// Mostly copied from frontend types +export type HogFunctionInputSchemaType = { + type: 'string' | 'number' | 'boolean' | 'dictionary' | 'choice' | 'json' + key: string + label?: string + choices?: { value: string; label: string }[] + required?: boolean + default?: any + secret?: boolean + description?: string +} + +export type HogFunctionType = { + id: string + team_id: number + name: string + enabled: boolean + hog: string + bytecode: HogBytecode + inputs_schema: HogFunctionInputSchemaType[] + inputs: Record< + string, + { + value: any + bytecode?: HogBytecode | object + } + > + filters?: HogFunctionFilters | null +} diff --git a/plugin-server/src/cdp/utils.ts b/plugin-server/src/cdp/utils.ts new file mode 100644 index 00000000000000..82f2739944dc83 --- /dev/null +++ b/plugin-server/src/cdp/utils.ts @@ -0,0 +1,93 @@ +// NOTE: PostIngestionEvent is our context event - it should never be sent directly to an output, but rather transformed into a lightweight schema + +import { GroupTypeToColumnIndex, RawClickHouseEvent, Team } from '../types' +import { clickHouseTimestampToISO } from '../utils/utils' +import { HogFunctionFilterGlobals, HogFunctionInvocationGlobals } from './types' + +// that we can keep to as a contract +export function convertToHogFunctionInvocationGlobals( + event: RawClickHouseEvent, + team: Team, + siteUrl: string, + groupTypes?: GroupTypeToColumnIndex +): HogFunctionInvocationGlobals { + const projectUrl = `${siteUrl}/project/${team.id}` + + const properties = event.properties ? JSON.parse(event.properties) : {} + if (event.elements_chain) { + properties['$elements_chain'] = event.elements_chain + } + + let groups: HogFunctionInvocationGlobals['groups'] = undefined + + if (groupTypes) { + groups = {} + + for (const [groupType, columnIndex] of Object.entries(groupTypes)) { + const groupKey = (properties[`$groups`] || {})[groupType] + const groupProperties = event[`group${columnIndex}_properties`] + + // TODO: Check that groupProperties always exist if the event is in that group + if (groupKey && groupProperties) { + const properties = JSON.parse(groupProperties) + + groups[groupType] = { + id: groupKey, + index: columnIndex, + type: groupType, + url: `${projectUrl}/groups/${columnIndex}/${encodeURIComponent(groupKey)}`, + properties, + } + } + } + } + const context: HogFunctionInvocationGlobals = { + project: { + id: team.id, + name: team.name, + url: projectUrl, + }, + event: { + // TODO: Element chain! + uuid: event.uuid, + name: event.event!, + distinct_id: event.distinct_id, + properties, + timestamp: clickHouseTimestampToISO(event.timestamp), + // TODO: generate url + url: `${projectUrl}/events/${encodeURIComponent(event.uuid)}/${encodeURIComponent( + clickHouseTimestampToISO(event.timestamp) + )}`, + }, + person: event.person_id + ? { + uuid: event.person_id, + properties: event.person_properties ? JSON.parse(event.person_properties) : {}, + // TODO: IS this distinct_id or person_id? + url: `${projectUrl}/person/${encodeURIComponent(event.distinct_id)}`, + } + : undefined, + groups, + } + + return context +} + +export function convertToHogFunctionFilterGlobal(globals: HogFunctionInvocationGlobals): HogFunctionFilterGlobals { + const groups: Record = {} + + for (const [_groupType, group] of Object.entries(globals.groups || {})) { + groups[`group_${group.index}`] = { + properties: group.properties, + } + } + + return { + event: globals.event.name, + elements_chain: globals.event.properties['$elements_chain'], + timestamp: globals.event.timestamp, + properties: globals.event.properties, + person: globals.person ? { properties: globals.person.properties } : undefined, + ...groups, + } +} diff --git a/plugin-server/src/main/ingestion-queues/session-recording/session-recordings-consumer.ts b/plugin-server/src/main/ingestion-queues/session-recording/session-recordings-consumer.ts index 8d7f2d79da3b9c..3be63ed2bb0c6c 100644 --- a/plugin-server/src/main/ingestion-queues/session-recording/session-recordings-consumer.ts +++ b/plugin-server/src/main/ingestion-queues/session-recording/session-recordings-consumer.ts @@ -333,11 +333,13 @@ export class SessionRecordingIngester { } public async handleEachBatch(messages: Message[], heartbeat: () => void): Promise { - status.info('🔁', `blob_ingester_consumer - handling batch`, { - size: messages.length, - partitionsInBatch: [...new Set(messages.map((x) => x.partition))], - assignedPartitions: this.assignedPartitions, - }) + if (messages.length !== 0) { + status.info('🔁', `blob_ingester_consumer - handling batch`, { + size: messages.length, + partitionsInBatch: [...new Set(messages.map((x) => x.partition))], + assignedPartitions: this.assignedPartitions, + }) + } await runInstrumentedFunction({ statsKey: `recordingingester.handleEachBatch`, sendTimeoutGuardToSentry: false, diff --git a/plugin-server/src/main/pluginsServer.ts b/plugin-server/src/main/pluginsServer.ts index 3a1ba51f879928..4cc219522003f1 100644 --- a/plugin-server/src/main/pluginsServer.ts +++ b/plugin-server/src/main/pluginsServer.ts @@ -10,7 +10,8 @@ import { Counter } from 'prom-client' import v8Profiler from 'v8-profiler-next' import { getPluginServerCapabilities } from '../capabilities' -import { buildIntegerMatcher, defaultConfig, sessionRecordingConsumerConfig } from '../config/config' +import { CdpProcessedEventsConsumer } from '../cdp/cdp-processed-events-consumer' +import { defaultConfig, sessionRecordingConsumerConfig } from '../config/config' import { Hub, PluginServerCapabilities, PluginsServerConfig } from '../types' import { createHub, createKafkaClient, createKafkaProducerWrapper } from '../utils/db/hub' import { PostgresRouter } from '../utils/db/postgres' @@ -105,6 +106,8 @@ export async function startPluginsServer( let onEventHandlerConsumer: KafkaJSIngestionConsumer | undefined let stopWebhooksHandlerConsumer: () => Promise | undefined + const shutdownCallbacks: (() => Promise)[] = [] + // Kafka consumer. Handles events that we couldn't find an existing person // to associate. The buffer handles delaying the ingestion of these events // (default 60 seconds) to allow for the person to be created in the @@ -157,6 +160,7 @@ export async function startPluginsServer( stopSessionRecordingBlobOverflowConsumer?.(), schedulerTasksConsumer?.disconnect(), personOverridesPeriodicTask?.stop(), + ...shutdownCallbacks.map((cb) => cb()), ]) if (piscina) { @@ -370,14 +374,7 @@ export async function startPluginsServer( const teamManager = hub?.teamManager ?? new TeamManager(postgres, serverConfig) const organizationManager = hub?.organizationManager ?? new OrganizationManager(postgres, teamManager) const KafkaProducerWrapper = hub?.kafkaProducer ?? (await createKafkaProducerWrapper(serverConfig)) - const rustyHook = - hub?.rustyHook ?? - new RustyHook( - buildIntegerMatcher(serverConfig.RUSTY_HOOK_FOR_TEAMS, true), - serverConfig.RUSTY_HOOK_ROLLOUT_PERCENTAGE, - serverConfig.RUSTY_HOOK_URL, - serverConfig.EXTERNAL_REQUEST_TIMEOUT_MS - ) + const rustyHook = hub?.rustyHook ?? new RustyHook(serverConfig) const appMetrics = hub?.appMetrics ?? new AppMetrics( @@ -494,6 +491,21 @@ export async function startPluginsServer( } } + if (capabilities.cdpProcessedEvents) { + ;[hub, closeHub] = hub ? [hub, closeHub] : await createHub(serverConfig, capabilities) + const consumer = new CdpProcessedEventsConsumer(serverConfig, hub) + await consumer.start() + + if (consumer.batchConsumer) { + shutdownOnConsumerExit(consumer.batchConsumer) + } + + shutdownCallbacks.push(async () => { + await consumer.stop() + }) + healthChecks['cdp-processed-events'] = () => consumer.isHealthy() ?? false + } + if (capabilities.personOverrides) { const postgres = hub?.postgres ?? new PostgresRouter(serverConfig) const kafkaProducer = hub?.kafkaProducer ?? (await createKafkaProducerWrapper(serverConfig)) diff --git a/plugin-server/src/types.ts b/plugin-server/src/types.ts index 7fffd6930bac23..8b3c74328afba6 100644 --- a/plugin-server/src/types.ts +++ b/plugin-server/src/types.ts @@ -81,6 +81,7 @@ export enum PluginServerMode { recordings_blob_ingestion = 'recordings-blob-ingestion', recordings_blob_ingestion_overflow = 'recordings-blob-ingestion-overflow', person_overrides = 'person-overrides', + cdp_processed_events = 'cdp-processed-events', } export const stringToPluginServerMode = Object.fromEntries( @@ -315,6 +316,7 @@ export interface PluginServerCapabilities { processAsyncWebhooksHandlers?: boolean sessionRecordingBlobIngestion?: boolean sessionRecordingBlobOverflowIngestion?: boolean + cdpProcessedEvents?: boolean personOverrides?: boolean appManagementSingleton?: boolean preflightSchedules?: boolean // Used for instance health checks on hobby deploy, not useful on cloud diff --git a/plugin-server/src/utils/db/hub.ts b/plugin-server/src/utils/db/hub.ts index 331f95c95d6bb4..359f1f9f7996aa 100644 --- a/plugin-server/src/utils/db/hub.ts +++ b/plugin-server/src/utils/db/hub.ts @@ -146,12 +146,7 @@ export async function createHub( const organizationManager = new OrganizationManager(postgres, teamManager) const pluginsApiKeyManager = new PluginsApiKeyManager(db) const rootAccessManager = new RootAccessManager(db) - const rustyHook = new RustyHook( - buildIntegerMatcher(serverConfig.RUSTY_HOOK_FOR_TEAMS, true), - serverConfig.RUSTY_HOOK_ROLLOUT_PERCENTAGE, - serverConfig.RUSTY_HOOK_URL, - serverConfig.EXTERNAL_REQUEST_TIMEOUT_MS - ) + const rustyHook = new RustyHook(serverConfig) const actionManager = new ActionManager(postgres, serverConfig) const actionMatcher = new ActionMatcher(postgres, actionManager, teamManager) diff --git a/plugin-server/src/worker/rusty-hook.ts b/plugin-server/src/worker/rusty-hook.ts index cb829800cbaa3c..a4d1c6c6b2d811 100644 --- a/plugin-server/src/worker/rusty-hook.ts +++ b/plugin-server/src/worker/rusty-hook.ts @@ -2,7 +2,8 @@ import { Webhook } from '@posthog/plugin-scaffold' import * as Sentry from '@sentry/node' import fetch from 'node-fetch' -import { ValueMatcher } from '../types' +import { buildIntegerMatcher } from '../config/config' +import { PluginsServerConfig, ValueMatcher } from '../types' import { isProdEnv } from '../utils/env-utils' import { raiseIfUserProvidedUrlUnsafe } from '../utils/fetch' import { status } from '../utils/status' @@ -23,12 +24,16 @@ interface RustyWebhookPayload { } export class RustyHook { + private enabledForTeams: ValueMatcher + constructor( - private enabledForTeams: ValueMatcher, - private rolloutPercentage: number, - private serviceUrl: string, - private requestTimeoutMs: number - ) {} + private serverConfig: Pick< + PluginsServerConfig, + 'RUSTY_HOOK_URL' | 'RUSTY_HOOK_FOR_TEAMS' | 'RUSTY_HOOK_ROLLOUT_PERCENTAGE' | 'EXTERNAL_REQUEST_TIMEOUT_MS' + > + ) { + this.enabledForTeams = buildIntegerMatcher(serverConfig.RUSTY_HOOK_FOR_TEAMS, true) + } public async enqueueIfEnabledForTeam({ webhook, @@ -43,7 +48,7 @@ export class RustyHook { }): Promise { // A simple and blunt rollout that just uses the last digits of the Team ID as a stable // selection against the `rolloutPercentage`. - const enabledByRolloutPercentage = (teamId % 1000) / 1000 < this.rolloutPercentage + const enabledByRolloutPercentage = (teamId % 1000) / 1000 < this.serverConfig.RUSTY_HOOK_ROLLOUT_PERCENTAGE if (!enabledByRolloutPercentage && !this.enabledForTeams(teamId)) { return false } @@ -75,14 +80,14 @@ export class RustyHook { const timer = new Date() try { attempt += 1 - const response = await fetch(this.serviceUrl, { + const response = await fetch(this.serverConfig.RUSTY_HOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body, // Sure, it's not an external request, but we should have a timeout and this is as // good as any. - timeout: this.requestTimeoutMs, + timeout: this.serverConfig.EXTERNAL_REQUEST_TIMEOUT_MS, }) if (response.ok) { diff --git a/plugin-server/tests/cdp/cdp-processed-events-consumer.test.ts b/plugin-server/tests/cdp/cdp-processed-events-consumer.test.ts new file mode 100644 index 00000000000000..9a26e5e089bbdf --- /dev/null +++ b/plugin-server/tests/cdp/cdp-processed-events-consumer.test.ts @@ -0,0 +1,146 @@ +import { CdpProcessedEventsConsumer } from '../../src/cdp/cdp-processed-events-consumer' +import { defaultConfig } from '../../src/config/config' +import { Hub, PluginsServerConfig, Team } from '../../src/types' +import { createHub } from '../../src/utils/db/hub' +import { getFirstTeam, resetTestDatabase } from '../helpers/sql' +import { HOG_EXAMPLES, HOG_FILTERS_EXAMPLES, HOG_INPUTS_EXAMPLES } from './examples' +import { createIncomingEvent, createMessage, insertHogFunction as _insertHogFunction } from './fixtures' + +const config: PluginsServerConfig = { + ...defaultConfig, +} + +const mockConsumer = { + on: jest.fn(), + commitSync: jest.fn(), + commit: jest.fn(), + queryWatermarkOffsets: jest.fn(), + committed: jest.fn(), + assignments: jest.fn(), + isConnected: jest.fn(() => true), + getMetadata: jest.fn(), +} + +jest.mock('../../src/kafka/batch-consumer', () => { + return { + startBatchConsumer: jest.fn(() => + Promise.resolve({ + join: () => ({ + finally: jest.fn(), + }), + stop: jest.fn(), + consumer: mockConsumer, + }) + ), + } +}) + +jest.mock('../../src/utils/fetch', () => { + return { + trackedFetch: jest.fn(() => Promise.resolve({ status: 200, text: () => Promise.resolve({}) })), + } +}) + +const mockFetch = require('../../src/utils/fetch').trackedFetch + +jest.setTimeout(1000) + +const noop = () => {} + +describe('CDP Processed Events Consuner', () => { + let processor: CdpProcessedEventsConsumer + let hub: Hub + let closeHub: () => Promise + let team: Team + + const insertHogFunction = async (hogFunction) => { + const item = await _insertHogFunction(hub.postgres, team, hogFunction) + // Trigger the reload that django would do + await processor.hogFunctionManager.reloadAllHogFunctions() + return item + } + + beforeAll(async () => { + await resetTestDatabase() + }) + + beforeEach(async () => { + ;[hub, closeHub] = await createHub() + team = await getFirstTeam(hub) + + processor = new CdpProcessedEventsConsumer(config, hub.postgres) + await processor.start() + }) + + afterEach(async () => { + jest.setTimeout(10000) + await processor.stop() + await closeHub() + }) + + afterAll(() => { + jest.useRealTimers() + }) + + describe('general event processing', () => { + /** + * Tests here are somewhat expensive so should mostly simulate happy paths and the more e2e scenarios + */ + it('can parse incoming messages correctly', async () => { + await insertHogFunction({ + ...HOG_EXAMPLES.simple_fetch, + ...HOG_INPUTS_EXAMPLES.simple_fetch, + ...HOG_FILTERS_EXAMPLES.no_filters, + }) + // Create a message that should be processed by this function + // Run the function and check that it was executed + await processor.handleEachBatch( + [ + createMessage( + createIncomingEvent(team.id, { + uuid: 'b3a1fe86-b10c-43cc-acaf-d208977608d0', + event: '$pageview', + properties: JSON.stringify({ + $lib_version: '1.0.0', + }), + }) + ), + ], + noop + ) + + expect(mockFetch).toHaveBeenCalledTimes(1) + expect(mockFetch.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "https://example.com/posthog-webhook", + Object { + "body": "{ + \\"event\\": { + \\"uuid\\": \\"b3a1fe86-b10c-43cc-acaf-d208977608d0\\", + \\"name\\": \\"$pageview\\", + \\"distinct_id\\": \\"distinct_id_1\\", + \\"properties\\": { + \\"$lib_version\\": \\"1.0.0\\", + \\"$elements_chain\\": \\"[]\\" + }, + \\"timestamp\\": null, + \\"url\\": \\"http://localhost:8000/project/2/events/b3a1fe86-b10c-43cc-acaf-d208977608d0/null\\" + }, + \\"groups\\": null, + \\"nested\\": { + \\"foo\\": \\"http://localhost:8000/project/2/events/b3a1fe86-b10c-43cc-acaf-d208977608d0/null\\" + }, + \\"person\\": null, + \\"event_url\\": \\"http://localhost:8000/project/2/events/b3a1fe86-b10c-43cc-acaf-d208977608d0/null-test\\" + }", + "headers": Object { + "version": "v=1.0.0", + }, + "method": "POST", + "timeout": 10000, + }, + ] + `) + }) + }) +}) diff --git a/plugin-server/tests/cdp/examples.ts b/plugin-server/tests/cdp/examples.ts new file mode 100644 index 00000000000000..9215d84c8026b5 --- /dev/null +++ b/plugin-server/tests/cdp/examples.ts @@ -0,0 +1,161 @@ +import { HogFunctionType } from '../../src/cdp/types' + +/** + * Hog functions are largely generated and built in the django service, making it tricky to test on this side. + * As such we have a bunch of prebuilt examples here for usage in tests. + */ +export const HOG_EXAMPLES: Record> = { + simple_fetch: { + hog: "fetch(inputs.url, {\n 'headers': inputs.headers,\n 'body': inputs.payload,\n 'method': inputs.method,\n 'payload': inputs.payload\n});", + bytecode: [ + '_h', + 32, + 'headers', + 32, + 'headers', + 32, + 'inputs', + 1, + 2, + 32, + 'body', + 32, + 'payload', + 32, + 'inputs', + 1, + 2, + 32, + 'method', + 32, + 'method', + 32, + 'inputs', + 1, + 2, + 32, + 'payload', + 32, + 'payload', + 32, + 'inputs', + 1, + 2, + 42, + 4, + 32, + 'url', + 32, + 'inputs', + 1, + 2, + 2, + 'fetch', + 2, + 35, + ], + }, +} + +export const HOG_INPUTS_EXAMPLES: Record> = { + simple_fetch: { + inputs_schema: [ + { key: 'url', type: 'string', label: 'Webhook URL', secret: false, required: true }, + { key: 'payload', type: 'json', label: 'JSON Payload', secret: false, required: true }, + { + key: 'method', + type: 'choice', + label: 'HTTP Method', + secret: false, + choices: [ + { label: 'POST', value: 'POST' }, + { label: 'PUT', value: 'PUT' }, + { label: 'PATCH', value: 'PATCH' }, + { label: 'GET', value: 'GET' }, + ], + required: true, + }, + { key: 'headers', type: 'dictionary', label: 'Headers', secret: false, required: false }, + ], + inputs: { + url: { + value: 'https://example.com/posthog-webhook', + bytecode: ['_h', 32, 'https://example.com/posthog-webhook'], + }, + method: { value: 'POST' }, + headers: { + value: { version: 'v={event.properties.$lib_version}' }, + bytecode: { + version: ['_h', 32, '$lib_version', 32, 'properties', 32, 'event', 1, 3, 32, 'v=', 2, 'concat', 2], + }, + }, + payload: { + value: { + event: '{event}', + groups: '{groups}', + nested: { foo: '{event.url}' }, + person: '{person}', + event_url: "{f'{event.url}-test'}", + }, + bytecode: { + event: ['_h', 32, 'event', 1, 1], + groups: ['_h', 32, 'groups', 1, 1], + nested: { foo: ['_h', 32, 'url', 32, 'event', 1, 2] }, + person: ['_h', 32, 'person', 1, 1], + event_url: ['_h', 32, '-test', 32, 'url', 32, 'event', 1, 2, 2, 'concat', 2], + }, + }, + }, + }, +} + +export const HOG_FILTERS_EXAMPLES: Record> = { + no_filters: { filters: { events: [], actions: [], bytecode: ['_h', 29] } }, + pageview_or_autocapture_filter: { + filters: { + events: [ + { + id: '$pageview', + name: '$pageview', + type: 'events', + order: 0, + properties: [{ key: '$current_url', type: 'event', value: 'posthog', operator: 'icontains' }], + }, + { id: '$autocapture', name: '$autocapture', type: 'events', order: 1 }, + ], + actions: [], + bytecode: [ + '_h', + 32, + '$autocapture', + 32, + 'event', + 1, + 1, + 11, + 3, + 1, + 32, + '%posthog%', + 32, + '$current_url', + 32, + 'properties', + 1, + 2, + 18, + 32, + '$pageview', + 32, + 'event', + 1, + 1, + 11, + 3, + 2, + 4, + 2, + ], + }, + }, +} diff --git a/plugin-server/tests/cdp/fixtures.ts b/plugin-server/tests/cdp/fixtures.ts new file mode 100644 index 00000000000000..8e6d836756cb52 --- /dev/null +++ b/plugin-server/tests/cdp/fixtures.ts @@ -0,0 +1,93 @@ +import { randomUUID } from 'crypto' +import { Message } from 'node-rdkafka' + +import { HogFunctionInvocationGlobals, HogFunctionType } from '../../src/cdp/types' +import { ClickHouseTimestamp, RawClickHouseEvent, Team } from '../../src/types' +import { PostgresRouter } from '../../src/utils/db/postgres' +import { insertRow } from '../helpers/sql' + +export const createHogFunction = (hogFunction: Partial) => { + const item: HogFunctionType = { + id: randomUUID(), + team_id: 1, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + created_by_id: 1001, + enabled: true, + deleted: false, + description: '', + hog: '', + ...hogFunction, + } + + return item +} + +export const createIncomingEvent = (teamId: number, data: Partial): RawClickHouseEvent => { + return { + team_id: teamId, + created_at: new Date().toISOString() as ClickHouseTimestamp, + elements_chain: '[]', + person_created_at: new Date().toISOString() as ClickHouseTimestamp, + person_properties: '{}', + distinct_id: 'distinct_id_1', + uuid: randomUUID(), + event: '$pageview', + timestamp: new Date().toISOString() as ClickHouseTimestamp, + properties: '{}', + ...data, + } +} + +export const createMessage = (event: RawClickHouseEvent, overrides: Partial = {}): Message => { + return { + partition: 1, + topic: 'test', + offset: 0, + timestamp: overrides.timestamp ?? Date.now(), + size: 1, + ...overrides, + value: Buffer.from(JSON.stringify(event)), + } +} + +export const insertHogFunction = async ( + postgres: PostgresRouter, + team: Team, + hogFunction: Partial = {} +) => { + const res = await insertRow( + postgres, + 'posthog_hogfunction', + createHogFunction({ + team_id: team.id, + ...hogFunction, + }) + ) + return res +} + +export const createHogExecutionGlobals = ( + data: Partial = {} +): HogFunctionInvocationGlobals => { + return { + ...data, + project: { + id: 1, + name: 'test', + url: 'http://localhost:8000/projects/1', + ...(data.project ?? {}), + }, + event: { + uuid: 'uuid', + name: 'test', + distinct_id: 'distinct_id', + url: 'http://localhost:8000/events/1', + properties: { + $lib_version: '1.2.3', + }, + timestamp: new Date().toISOString(), + ...(data.event ?? {}), + }, + } +} diff --git a/plugin-server/tests/cdp/hog-executor.test.ts b/plugin-server/tests/cdp/hog-executor.test.ts new file mode 100644 index 00000000000000..5b2683bb975a8f --- /dev/null +++ b/plugin-server/tests/cdp/hog-executor.test.ts @@ -0,0 +1,127 @@ +import { HogExecutor } from '../../src/cdp/hog-executor' +import { HogFunctionManager } from '../../src/cdp/hog-function-manager' +import { defaultConfig } from '../../src/config/config' +import { PluginsServerConfig } from '../../src/types' +import { RustyHook } from '../../src/worker/rusty-hook' +import { HOG_EXAMPLES, HOG_FILTERS_EXAMPLES, HOG_INPUTS_EXAMPLES } from './examples' +import { createHogExecutionGlobals, createHogFunction, insertHogFunction as _insertHogFunction } from './fixtures' + +const config: PluginsServerConfig = { + ...defaultConfig, +} + +jest.mock('../../src/utils/fetch', () => { + return { + trackedFetch: jest.fn(() => Promise.resolve({ status: 200, text: () => Promise.resolve({}) })), + } +}) + +const mockFetch = require('../../src/utils/fetch').trackedFetch + +describe('Hog Executor', () => { + jest.setTimeout(1000) + let executor: HogExecutor + + const mockFunctionManager = { + reloadAllHogFunctions: jest.fn(), + getTeamHogFunctions: jest.fn(), + } + + const mockRustyHook = { + enqueueIfEnabledForTeam: jest.fn(() => true), + } + + beforeEach(() => { + jest.useFakeTimers() + jest.setSystemTime(new Date('2024-06-07T12:00:00.000Z').getTime()) + executor = new HogExecutor( + config, + mockFunctionManager as any as HogFunctionManager, + mockRustyHook as any as RustyHook + ) + }) + + describe('general event processing', () => { + /** + * Tests here are somewhat expensive so should mostly simulate happy paths and the more e2e scenarios + */ + it('can parse incoming messages correctly', async () => { + const fn = createHogFunction({ + ...HOG_EXAMPLES.simple_fetch, + ...HOG_INPUTS_EXAMPLES.simple_fetch, + ...HOG_FILTERS_EXAMPLES.no_filters, + }) + + mockFunctionManager.getTeamHogFunctions.mockReturnValue({ + [1]: fn, + }) + + // Create a message that should be processed by this function + // Run the function and check that it was executed + await executor.executeMatchingFunctions({ + globals: createHogExecutionGlobals(), + }) + + expect(mockFetch).toHaveBeenCalledTimes(1) + expect(mockFetch.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "https://example.com/posthog-webhook", + Object { + "body": "{ + \\"event\\": { + \\"uuid\\": \\"uuid\\", + \\"name\\": \\"test\\", + \\"distinct_id\\": \\"distinct_id\\", + \\"url\\": \\"http://localhost:8000/events/1\\", + \\"properties\\": { + \\"$lib_version\\": \\"1.2.3\\" + }, + \\"timestamp\\": \\"2024-06-07T12:00:00.000Z\\" + }, + \\"groups\\": null, + \\"nested\\": { + \\"foo\\": \\"http://localhost:8000/events/1\\" + }, + \\"person\\": null, + \\"event_url\\": \\"http://localhost:8000/events/1-test\\" + }", + "headers": Object { + "version": "v=1.2.3", + }, + "method": "POST", + "timeout": 10000, + }, + ] + `) + }) + // NOTE: Will be fixed in follow up + it('can filters incoming messages correctly', async () => { + const fn = createHogFunction({ + ...HOG_EXAMPLES.simple_fetch, + ...HOG_INPUTS_EXAMPLES.simple_fetch, + ...HOG_FILTERS_EXAMPLES.pageview_or_autocapture_filter, + }) + + mockFunctionManager.getTeamHogFunctions.mockReturnValue({ + [1]: fn, + }) + + const resultsShouldntMatch = await executor.executeMatchingFunctions({ + globals: createHogExecutionGlobals(), + }) + expect(resultsShouldntMatch).toHaveLength(0) + + const resultsShouldMatch = await executor.executeMatchingFunctions({ + globals: createHogExecutionGlobals({ + event: { + name: '$pageview', + properties: { + $current_url: 'https://posthog.com', + }, + } as any, + }), + }) + expect(resultsShouldMatch).toHaveLength(1) + }) + }) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4789afdebaff46..12e300d9cae7b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22092,4 +22092,4 @@ packages: /zxcvbn@4.4.2: resolution: {integrity: sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ==} - dev: false + dev: false \ No newline at end of file diff --git a/posthog/api/__init__.py b/posthog/api/__init__.py index 9e1f2d84588865..dbe3d1bd175157 100644 --- a/posthog/api/__init__.py +++ b/posthog/api/__init__.py @@ -20,6 +20,7 @@ event_definition, exports, feature_flag, + hog_function, ingestion_warnings, instance_settings, instance_status, @@ -408,6 +409,13 @@ def api_not_found(request): ["team_id"], ) +projects_router.register( + r"hog_functions", + hog_function.HogFunctionViewSet, + "project_hog_functions", + ["team_id"], +) + projects_router.register( r"alerts", alert.AlertViewSet, diff --git a/posthog/api/app_metrics.py b/posthog/api/app_metrics.py index 8f97638cd0fbfe..61612980e24f5f 100644 --- a/posthog/api/app_metrics.py +++ b/posthog/api/app_metrics.py @@ -56,23 +56,30 @@ def retrieve(self, request: request.Request, *args: Any, **kwargs: Any) -> respo except ValueError: pass - plugin_config = self.get_object() - filter = AppMetricsRequestSerializer(data=request.query_params) filter.is_valid(raise_exception=True) - metric_results = AppMetricsQuery(self.team, plugin_config.pk, filter).run() - errors = AppMetricsErrorsQuery(self.team, plugin_config.pk, filter).run() + if "hog-" in kwargs["pk"]: + # TODO: Make app metrics work with string IDs + metric_results = { + "dates": [], + "successes": [], + "successes_on_retry": [], + "failures": [], + "totals": {"successes": 0, "successes_on_retry": 0, "failures": 0}, + } + errors = [] + else: + metric_results = AppMetricsQuery(self.team, kwargs["pk"], filter).run() + errors = AppMetricsErrorsQuery(self.team, kwargs["pk"], filter).run() return response.Response({"metrics": metric_results, "errors": errors}) @action(methods=["GET"], detail=True) def error_details(self, request: request.Request, *args: Any, **kwargs: Any) -> response.Response: - plugin_config = self.get_object() - filter = AppMetricsErrorsRequestSerializer(data=request.query_params) filter.is_valid(raise_exception=True) - error_details = AppMetricsErrorDetailsQuery(self.team, plugin_config.pk, filter).run() + error_details = AppMetricsErrorDetailsQuery(self.team, kwargs["pk"], filter).run() return response.Response({"result": error_details}) def get_batch_export_runs_app_metrics_queryset(self, batch_export_id: str): diff --git a/posthog/api/hog_function.py b/posthog/api/hog_function.py new file mode 100644 index 00000000000000..e4c88f13afbddd --- /dev/null +++ b/posthog/api/hog_function.py @@ -0,0 +1,180 @@ +import structlog +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import serializers, viewsets +from rest_framework.serializers import BaseSerializer + +from posthog.api.forbid_destroy_model import ForbidDestroyModel +from posthog.api.routing import TeamAndOrgViewSetMixin +from posthog.api.shared import UserBasicSerializer +from posthog.hogql.bytecode import create_bytecode +from posthog.hogql.parser import parse_program +from posthog.models.hog_functions.hog_function import HogFunction +from posthog.models.hog_functions.utils import generate_template_bytecode +from posthog.permissions import PostHogFeatureFlagPermission + + +logger = structlog.get_logger(__name__) + + +class InputsSchemaItemSerializer(serializers.Serializer): + type = serializers.ChoiceField(choices=["string", "boolean", "dictionary", "choice", "json"]) + key = serializers.CharField() + label = serializers.CharField(required=False) # type: ignore + choices = serializers.ListField(child=serializers.DictField(), required=False) + required = serializers.BooleanField(default=False) # type: ignore + default = serializers.JSONField(required=False) + secret = serializers.BooleanField(default=False) + description = serializers.CharField(required=False) + + # TODO Validate choices if type=choice + + +class AnyInputField(serializers.Field): + def to_internal_value(self, data): + return data + + def to_representation(self, value): + return value + + +class InputsItemSerializer(serializers.Serializer): + value = AnyInputField(required=False) + bytecode = serializers.ListField(required=False, read_only=True) + + def validate(self, attrs): + schema = self.context["schema"] + value = attrs.get("value") + + if schema.get("required") and not value: + raise serializers.ValidationError("This field is required.") + + if not value: + return attrs + + name: str = schema["key"] + item_type = schema["type"] + value = attrs["value"] + + # Validate each type + if item_type == "string": + if not isinstance(value, str): + raise serializers.ValidationError("Value must be a string.") + elif item_type == "boolean": + if not isinstance(value, bool): + raise serializers.ValidationError("Value must be a boolean.") + elif item_type == "dictionary": + if not isinstance(value, dict): + raise serializers.ValidationError("Value must be a dictionary.") + + try: + if value: + if item_type in ["string", "dictionary", "json"]: + attrs["bytecode"] = generate_template_bytecode(value) + except Exception as e: + raise serializers.ValidationError({"inputs": {name: f"Invalid template: {str(e)}"}}) + + return attrs + + +class HogFunctionMinimalSerializer(serializers.ModelSerializer): + created_by = UserBasicSerializer(read_only=True) + + class Meta: + model = HogFunction + fields = [ + "id", + "name", + "description", + "created_at", + "created_by", + "updated_at", + "enabled", + "hog", + "filters", + ] + read_only_fields = fields + + +class HogFunctionSerializer(HogFunctionMinimalSerializer): + class Meta: + model = HogFunction + fields = [ + "id", + "name", + "description", + "created_at", + "created_by", + "updated_at", + "enabled", + "hog", + "bytecode", + "inputs_schema", + "inputs", + "filters", + ] + read_only_fields = [ + "id", + "created_at", + "created_by", + "updated_at", + "bytecode", + ] + + def validate_inputs_schema(self, value): + if not isinstance(value, list): + raise serializers.ValidationError("inputs_schema must be a list of objects.") + + serializer = InputsSchemaItemSerializer(data=value, many=True) + + if not serializer.is_valid(): + raise serializers.ValidationError(serializer.errors) + + return serializer.validated_data or [] + + def validate(self, attrs): + team = self.context["get_team"]() + attrs["team"] = team + attrs["inputs_schema"] = attrs.get("inputs_schema", []) + attrs["inputs"] = attrs.get("inputs", {}) + attrs["filters"] = attrs.get("filters", {}) + + validated_inputs = {} + + for schema in attrs["inputs_schema"]: + value = attrs["inputs"].get(schema["key"], {}) + serializer = InputsItemSerializer(data=value, context={"schema": schema}) + + if not serializer.is_valid(): + first_error = next(iter(serializer.errors.values()))[0] + raise serializers.ValidationError({"inputs": {schema["key"]: first_error}}) + + validated_inputs[schema["key"]] = serializer.validated_data + + attrs["inputs"] = validated_inputs + + # Attempt to compile the hog + try: + program = parse_program(attrs["hog"]) + attrs["bytecode"] = create_bytecode(program, supported_functions={"fetch"}) + except Exception as e: + raise serializers.ValidationError({"hog": str(e)}) + + return attrs + + def create(self, validated_data: dict, *args, **kwargs) -> HogFunction: + request = self.context["request"] + validated_data["created_by"] = request.user + return super().create(validated_data=validated_data) + + +class HogFunctionViewSet(TeamAndOrgViewSetMixin, ForbidDestroyModel, viewsets.ModelViewSet): + scope_object = "INTERNAL" # Keep internal until we are happy to release this GA + queryset = HogFunction.objects.all() + filter_backends = [DjangoFilterBackend] + filterset_fields = ["id", "team", "created_by", "enabled"] + + permission_classes = [PostHogFeatureFlagPermission] + posthog_feature_flag = {"hog-functions": ["create", "partial_update", "update"]} + + def get_serializer_class(self) -> type[BaseSerializer]: + return HogFunctionMinimalSerializer if self.action == "list" else HogFunctionSerializer diff --git a/posthog/api/test/__snapshots__/test_decide.ambr b/posthog/api/test/__snapshots__/test_decide.ambr index eb0091a95ba5c9..d03538893572b3 100644 --- a/posthog/api/test/__snapshots__/test_decide.ambr +++ b/posthog/api/test/__snapshots__/test_decide.ambr @@ -66,6 +66,29 @@ ''' # --- # name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.10 + ''' + SELECT "posthog_featureflag"."id", + "posthog_featureflag"."key", + "posthog_featureflag"."name", + "posthog_featureflag"."filters", + "posthog_featureflag"."rollout_percentage", + "posthog_featureflag"."team_id", + "posthog_featureflag"."created_by_id", + "posthog_featureflag"."created_at", + "posthog_featureflag"."deleted", + "posthog_featureflag"."active", + "posthog_featureflag"."rollback_conditions", + "posthog_featureflag"."performed_rollback", + "posthog_featureflag"."ensure_experience_continuity", + "posthog_featureflag"."usage_dashboard_id", + "posthog_featureflag"."has_enriched_analytics" + FROM "posthog_featureflag" + WHERE ("posthog_featureflag"."active" + AND NOT "posthog_featureflag"."deleted" + AND "posthog_featureflag"."team_id" = 2) + ''' +# --- +# name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.11 ''' SELECT "posthog_pluginconfig"."id", "posthog_pluginconfig"."web_token", @@ -81,17 +104,6 @@ AND "posthog_pluginconfig"."team_id" = 2) ''' # --- -# name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.11 - ''' - SELECT "posthog_instancesetting"."id", - "posthog_instancesetting"."key", - "posthog_instancesetting"."raw_value" - FROM "posthog_instancesetting" - WHERE "posthog_instancesetting"."key" = 'constance:posthog:PERSON_ON_EVENTS_V2_ENABLED' - ORDER BY "posthog_instancesetting"."id" ASC - LIMIT 1 - ''' -# --- # name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.12 ''' SELECT "posthog_instancesetting"."id", @@ -316,6 +328,83 @@ ''' # --- # name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.7 + ''' + SELECT "posthog_hogfunction"."id", + "posthog_hogfunction"."team_id", + "posthog_hogfunction"."name", + "posthog_hogfunction"."description", + "posthog_hogfunction"."created_at", + "posthog_hogfunction"."created_by_id", + "posthog_hogfunction"."deleted", + "posthog_hogfunction"."updated_at", + "posthog_hogfunction"."enabled", + "posthog_hogfunction"."hog", + "posthog_hogfunction"."bytecode", + "posthog_hogfunction"."inputs_schema", + "posthog_hogfunction"."inputs", + "posthog_hogfunction"."filters", + "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."project_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."has_completed_onboarding_for", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."session_recording_sample_rate", + "posthog_team"."session_recording_minimum_duration_milliseconds", + "posthog_team"."session_recording_linked_flag", + "posthog_team"."session_recording_network_payload_capture_config", + "posthog_team"."session_replay_config", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."surveys_opt_in", + "posthog_team"."heatmaps_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."week_start_day", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."modifiers", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days", + "posthog_team"."plugins_opt_in", + "posthog_team"."opt_out_capture", + "posthog_team"."event_names", + "posthog_team"."event_names_with_usage", + "posthog_team"."event_properties", + "posthog_team"."event_properties_with_usage", + "posthog_team"."event_properties_numerical", + "posthog_team"."external_data_workspace_id", + "posthog_team"."external_data_workspace_last_synced_at" + FROM "posthog_hogfunction" + INNER JOIN "posthog_team" ON ("posthog_hogfunction"."team_id" = "posthog_team"."id") + WHERE ("posthog_hogfunction"."team_id" = 2 + AND "posthog_hogfunction"."filters" @> '{"filter_test_accounts": true}'::jsonb) + ''' +# --- +# name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.8 ''' SELECT 1 AS "a" FROM "posthog_grouptypemapping" @@ -323,7 +412,7 @@ LIMIT 1 ''' # --- -# name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.8 +# name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.9 ''' SELECT "posthog_user"."id", "posthog_user"."password", @@ -355,30 +444,84 @@ LIMIT 21 ''' # --- -# name: TestDecide.test_decide_doesnt_error_out_when_database_is_down.9 +# name: TestDecide.test_flag_with_behavioural_cohorts ''' - SELECT "posthog_featureflag"."id", - "posthog_featureflag"."key", - "posthog_featureflag"."name", - "posthog_featureflag"."filters", - "posthog_featureflag"."rollout_percentage", - "posthog_featureflag"."team_id", - "posthog_featureflag"."created_by_id", - "posthog_featureflag"."created_at", - "posthog_featureflag"."deleted", - "posthog_featureflag"."active", - "posthog_featureflag"."rollback_conditions", - "posthog_featureflag"."performed_rollback", - "posthog_featureflag"."ensure_experience_continuity", - "posthog_featureflag"."usage_dashboard_id", - "posthog_featureflag"."has_enriched_analytics" - FROM "posthog_featureflag" - WHERE ("posthog_featureflag"."active" - AND NOT "posthog_featureflag"."deleted" - AND "posthog_featureflag"."team_id" = 2) + SELECT "posthog_hogfunction"."id", + "posthog_hogfunction"."team_id", + "posthog_hogfunction"."name", + "posthog_hogfunction"."description", + "posthog_hogfunction"."created_at", + "posthog_hogfunction"."created_by_id", + "posthog_hogfunction"."deleted", + "posthog_hogfunction"."updated_at", + "posthog_hogfunction"."enabled", + "posthog_hogfunction"."hog", + "posthog_hogfunction"."bytecode", + "posthog_hogfunction"."inputs_schema", + "posthog_hogfunction"."inputs", + "posthog_hogfunction"."filters", + "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."project_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."has_completed_onboarding_for", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."session_recording_sample_rate", + "posthog_team"."session_recording_minimum_duration_milliseconds", + "posthog_team"."session_recording_linked_flag", + "posthog_team"."session_recording_network_payload_capture_config", + "posthog_team"."session_replay_config", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."surveys_opt_in", + "posthog_team"."heatmaps_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."week_start_day", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."modifiers", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days", + "posthog_team"."plugins_opt_in", + "posthog_team"."opt_out_capture", + "posthog_team"."event_names", + "posthog_team"."event_names_with_usage", + "posthog_team"."event_properties", + "posthog_team"."event_properties_with_usage", + "posthog_team"."event_properties_numerical", + "posthog_team"."external_data_workspace_id", + "posthog_team"."external_data_workspace_last_synced_at" + FROM "posthog_hogfunction" + INNER JOIN "posthog_team" ON ("posthog_hogfunction"."team_id" = "posthog_team"."id") + WHERE ("posthog_hogfunction"."team_id" = 2 + AND "posthog_hogfunction"."filters" @> '{"filter_test_accounts": true}'::jsonb) ''' # --- -# name: TestDecide.test_flag_with_behavioural_cohorts +# name: TestDecide.test_flag_with_behavioural_cohorts.1 ''' SELECT "posthog_user"."id", "posthog_user"."password", @@ -410,7 +553,7 @@ LIMIT 21 ''' # --- -# name: TestDecide.test_flag_with_behavioural_cohorts.1 +# name: TestDecide.test_flag_with_behavioural_cohorts.2 ''' SELECT "posthog_team"."id", "posthog_team"."uuid", @@ -472,7 +615,7 @@ LIMIT 21 ''' # --- -# name: TestDecide.test_flag_with_behavioural_cohorts.2 +# name: TestDecide.test_flag_with_behavioural_cohorts.3 ''' SELECT "posthog_featureflag"."id", "posthog_featureflag"."key", @@ -495,7 +638,7 @@ AND "posthog_featureflag"."team_id" = 2) ''' # --- -# name: TestDecide.test_flag_with_behavioural_cohorts.3 +# name: TestDecide.test_flag_with_behavioural_cohorts.4 ''' SELECT "posthog_cohort"."id", "posthog_cohort"."name", @@ -519,7 +662,7 @@ AND "posthog_cohort"."team_id" = 2) ''' # --- -# name: TestDecide.test_flag_with_behavioural_cohorts.4 +# name: TestDecide.test_flag_with_behavioural_cohorts.5 ''' SELECT "posthog_cohort"."id", "posthog_cohort"."name", @@ -544,6 +687,83 @@ ''' # --- # name: TestDecide.test_flag_with_regular_cohorts + ''' + SELECT "posthog_hogfunction"."id", + "posthog_hogfunction"."team_id", + "posthog_hogfunction"."name", + "posthog_hogfunction"."description", + "posthog_hogfunction"."created_at", + "posthog_hogfunction"."created_by_id", + "posthog_hogfunction"."deleted", + "posthog_hogfunction"."updated_at", + "posthog_hogfunction"."enabled", + "posthog_hogfunction"."hog", + "posthog_hogfunction"."bytecode", + "posthog_hogfunction"."inputs_schema", + "posthog_hogfunction"."inputs", + "posthog_hogfunction"."filters", + "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."project_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."has_completed_onboarding_for", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."session_recording_sample_rate", + "posthog_team"."session_recording_minimum_duration_milliseconds", + "posthog_team"."session_recording_linked_flag", + "posthog_team"."session_recording_network_payload_capture_config", + "posthog_team"."session_replay_config", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."surveys_opt_in", + "posthog_team"."heatmaps_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."week_start_day", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."modifiers", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days", + "posthog_team"."plugins_opt_in", + "posthog_team"."opt_out_capture", + "posthog_team"."event_names", + "posthog_team"."event_names_with_usage", + "posthog_team"."event_properties", + "posthog_team"."event_properties_with_usage", + "posthog_team"."event_properties_numerical", + "posthog_team"."external_data_workspace_id", + "posthog_team"."external_data_workspace_last_synced_at" + FROM "posthog_hogfunction" + INNER JOIN "posthog_team" ON ("posthog_hogfunction"."team_id" = "posthog_team"."id") + WHERE ("posthog_hogfunction"."team_id" = 2 + AND "posthog_hogfunction"."filters" @> '{"filter_test_accounts": true}'::jsonb) + ''' +# --- +# name: TestDecide.test_flag_with_regular_cohorts.1 ''' SELECT "posthog_user"."id", "posthog_user"."password", @@ -575,7 +795,7 @@ LIMIT 21 ''' # --- -# name: TestDecide.test_flag_with_regular_cohorts.1 +# name: TestDecide.test_flag_with_regular_cohorts.2 ''' SELECT "posthog_team"."id", "posthog_team"."uuid", @@ -637,7 +857,7 @@ LIMIT 21 ''' # --- -# name: TestDecide.test_flag_with_regular_cohorts.2 +# name: TestDecide.test_flag_with_regular_cohorts.3 ''' SELECT "posthog_featureflag"."id", "posthog_featureflag"."key", @@ -660,7 +880,7 @@ AND "posthog_featureflag"."team_id" = 2) ''' # --- -# name: TestDecide.test_flag_with_regular_cohorts.3 +# name: TestDecide.test_flag_with_regular_cohorts.4 ''' SELECT "posthog_cohort"."id", "posthog_cohort"."name", @@ -684,7 +904,7 @@ AND "posthog_cohort"."team_id" = 2) ''' # --- -# name: TestDecide.test_flag_with_regular_cohorts.4 +# name: TestDecide.test_flag_with_regular_cohorts.5 ''' SELECT (("posthog_person"."properties" -> '$some_prop_1') = '"something_1"'::jsonb AND "posthog_person"."properties" ? '$some_prop_1' @@ -696,7 +916,7 @@ AND "posthog_person"."team_id" = 2) ''' # --- -# name: TestDecide.test_flag_with_regular_cohorts.5 +# name: TestDecide.test_flag_with_regular_cohorts.6 ''' SELECT "posthog_cohort"."id", "posthog_cohort"."name", @@ -720,7 +940,7 @@ AND "posthog_cohort"."team_id" = 2) ''' # --- -# name: TestDecide.test_flag_with_regular_cohorts.6 +# name: TestDecide.test_flag_with_regular_cohorts.7 ''' SELECT (("posthog_person"."properties" -> '$some_prop_1') = '"something_1"'::jsonb AND "posthog_person"."properties" ? '$some_prop_1' @@ -827,6 +1047,83 @@ ''' # --- # name: TestDecide.test_web_app_queries.3 + ''' + SELECT "posthog_hogfunction"."id", + "posthog_hogfunction"."team_id", + "posthog_hogfunction"."name", + "posthog_hogfunction"."description", + "posthog_hogfunction"."created_at", + "posthog_hogfunction"."created_by_id", + "posthog_hogfunction"."deleted", + "posthog_hogfunction"."updated_at", + "posthog_hogfunction"."enabled", + "posthog_hogfunction"."hog", + "posthog_hogfunction"."bytecode", + "posthog_hogfunction"."inputs_schema", + "posthog_hogfunction"."inputs", + "posthog_hogfunction"."filters", + "posthog_team"."id", + "posthog_team"."uuid", + "posthog_team"."organization_id", + "posthog_team"."project_id", + "posthog_team"."api_token", + "posthog_team"."app_urls", + "posthog_team"."name", + "posthog_team"."slack_incoming_webhook", + "posthog_team"."created_at", + "posthog_team"."updated_at", + "posthog_team"."anonymize_ips", + "posthog_team"."completed_snippet_onboarding", + "posthog_team"."has_completed_onboarding_for", + "posthog_team"."ingested_event", + "posthog_team"."autocapture_opt_out", + "posthog_team"."autocapture_exceptions_opt_in", + "posthog_team"."autocapture_exceptions_errors_to_ignore", + "posthog_team"."session_recording_opt_in", + "posthog_team"."session_recording_sample_rate", + "posthog_team"."session_recording_minimum_duration_milliseconds", + "posthog_team"."session_recording_linked_flag", + "posthog_team"."session_recording_network_payload_capture_config", + "posthog_team"."session_replay_config", + "posthog_team"."capture_console_log_opt_in", + "posthog_team"."capture_performance_opt_in", + "posthog_team"."surveys_opt_in", + "posthog_team"."heatmaps_opt_in", + "posthog_team"."session_recording_version", + "posthog_team"."signup_token", + "posthog_team"."is_demo", + "posthog_team"."access_control", + "posthog_team"."week_start_day", + "posthog_team"."inject_web_apps", + "posthog_team"."test_account_filters", + "posthog_team"."test_account_filters_default_checked", + "posthog_team"."path_cleaning_filters", + "posthog_team"."timezone", + "posthog_team"."data_attributes", + "posthog_team"."person_display_name_properties", + "posthog_team"."live_events_columns", + "posthog_team"."recording_domains", + "posthog_team"."primary_dashboard_id", + "posthog_team"."extra_settings", + "posthog_team"."modifiers", + "posthog_team"."correlation_config", + "posthog_team"."session_recording_retention_period_days", + "posthog_team"."plugins_opt_in", + "posthog_team"."opt_out_capture", + "posthog_team"."event_names", + "posthog_team"."event_names_with_usage", + "posthog_team"."event_properties", + "posthog_team"."event_properties_with_usage", + "posthog_team"."event_properties_numerical", + "posthog_team"."external_data_workspace_id", + "posthog_team"."external_data_workspace_last_synced_at" + FROM "posthog_hogfunction" + INNER JOIN "posthog_team" ON ("posthog_hogfunction"."team_id" = "posthog_team"."id") + WHERE ("posthog_hogfunction"."team_id" = 2 + AND "posthog_hogfunction"."filters" @> '{"filter_test_accounts": true}'::jsonb) + ''' +# --- +# name: TestDecide.test_web_app_queries.4 ''' SELECT "posthog_pluginconfig"."id", "posthog_pluginconfig"."web_token", @@ -842,7 +1139,7 @@ AND "posthog_pluginconfig"."team_id" = 2) ''' # --- -# name: TestDecide.test_web_app_queries.4 +# name: TestDecide.test_web_app_queries.5 ''' SELECT "posthog_pluginconfig"."id", "posthog_pluginconfig"."web_token", diff --git a/posthog/api/test/test_hog_function.py b/posthog/api/test/test_hog_function.py new file mode 100644 index 00000000000000..63b6fbc22ec93d --- /dev/null +++ b/posthog/api/test/test_hog_function.py @@ -0,0 +1,287 @@ +import json +from unittest.mock import ANY, patch + +from rest_framework import status + +from posthog.models.action.action import Action +from posthog.test.base import APIBaseTest, ClickhouseTestMixin, QueryMatchingTest + + +EXAMPLE_FULL = { + "name": "HogHook", + "hog": "fetch(inputs.url, {\n 'headers': inputs.headers,\n 'body': inputs.payload,\n 'method': inputs.method\n});", + "inputs_schema": [ + {"key": "url", "type": "string", "label": "Webhook URL", "required": True}, + {"key": "payload", "type": "json", "label": "JSON Payload", "required": True}, + { + "key": "method", + "type": "choice", + "label": "HTTP Method", + "choices": [ + {"label": "POST", "value": "POST"}, + {"label": "PUT", "value": "PUT"}, + {"label": "PATCH", "value": "PATCH"}, + {"label": "GET", "value": "GET"}, + ], + "required": True, + }, + {"key": "headers", "type": "dictionary", "label": "Headers", "required": False}, + ], + "inputs": { + "url": { + "value": "http://localhost:2080/0e02d917-563f-4050-9725-aad881b69937", + }, + "method": {"value": "POST"}, + "headers": { + "value": {"version": "v={event.properties.$lib_version}"}, + }, + "payload": { + "value": { + "event": "{event}", + "groups": "{groups}", + "nested": {"foo": "{event.url}"}, + "person": "{person}", + "event_url": "{f'{event.url}-test'}", + }, + }, + }, + "filters": { + "events": [{"id": "$pageview", "name": "$pageview", "type": "events", "order": 0}], + "actions": [{"id": "9", "name": "Test Action", "type": "actions", "order": 1}], + "filter_test_accounts": True, + }, +} + + +class TestHogFunctionAPI(ClickhouseTestMixin, APIBaseTest, QueryMatchingTest): + @patch("posthog.permissions.posthoganalytics.feature_enabled") + def test_create_hog_function_forbidden_if_not_in_flag(self, mock_feature_enabled): + mock_feature_enabled.return_value = False + + response = self.client.post( + f"/api/projects/{self.team.id}/hog_functions/", + data={ + "name": "Fetch URL", + "description": "Test description", + "hog": "fetch(inputs.url);", + }, + ) + assert response.status_code == status.HTTP_403_FORBIDDEN, response.json() + + assert mock_feature_enabled.call_count == 1 + assert mock_feature_enabled.call_args[0][0] == ("hog-functions") + + @patch("posthog.permissions.posthoganalytics.feature_enabled", return_value=True) + def test_create_hog_function(self, *args): + response = self.client.post( + f"/api/projects/{self.team.id}/hog_functions/", + data={ + "name": "Fetch URL", + "description": "Test description", + "hog": "fetch(inputs.url);", + }, + ) + assert response.status_code == status.HTTP_201_CREATED, response.json() + assert response.json()["created_by"]["id"] == self.user.id + assert response.json() == { + "id": ANY, + "name": "Fetch URL", + "description": "Test description", + "created_at": ANY, + "created_by": ANY, + "updated_at": ANY, + "enabled": False, + "hog": "fetch(inputs.url);", + "bytecode": ["_h", 32, "url", 32, "inputs", 1, 2, 2, "fetch", 1, 35], + "inputs_schema": [], + "inputs": {}, + "filters": {"bytecode": ["_h", 29]}, + } + + @patch("posthog.permissions.posthoganalytics.feature_enabled", return_value=True) + def test_inputs_required(self, *args): + payload = { + "name": "Fetch URL", + "hog": "fetch(inputs.url);", + "inputs_schema": [ + {"key": "url", "type": "string", "label": "Webhook URL", "required": True}, + ], + } + # Check required + res = self.client.post(f"/api/projects/{self.team.id}/hog_functions/", data={**payload}) + assert res.status_code == status.HTTP_400_BAD_REQUEST, res.json() + assert res.json() == { + "type": "validation_error", + "code": "invalid_input", + "detail": "This field is required.", + "attr": "inputs__url", + } + + @patch("posthog.permissions.posthoganalytics.feature_enabled", return_value=True) + def test_inputs_mismatch_type(self, *args): + payload = { + "name": "Fetch URL", + "hog": "fetch(inputs.url);", + "inputs_schema": [ + {"key": "string", "type": "string"}, + {"key": "dictionary", "type": "dictionary"}, + {"key": "boolean", "type": "boolean"}, + ], + } + + bad_inputs = { + "string": 123, + "dictionary": 123, + "boolean": 123, + } + + for key, value in bad_inputs.items(): + res = self.client.post( + f"/api/projects/{self.team.id}/hog_functions/", data={**payload, "inputs": {key: {"value": value}}} + ) + assert res.json() == { + "type": "validation_error", + "code": "invalid_input", + "detail": f"Value must be a {key}.", + "attr": f"inputs__{key}", + }, f"Did not get error for {key}, got {res.json()}" + assert res.status_code == status.HTTP_400_BAD_REQUEST, res.json() + + @patch("posthog.permissions.posthoganalytics.feature_enabled", return_value=True) + def test_generates_hog_bytecode(self, *args): + response = self.client.post( + f"/api/projects/{self.team.id}/hog_functions/", + data={ + "name": "Fetch URL", + "hog": "let i := 0;\nwhile(i < 3) {\n i := i + 1;\n fetch(inputs.url, {\n 'headers': {\n 'x-count': f'{i}'\n },\n 'body': inputs.payload,\n 'method': inputs.method\n });\n}", + }, + ) + # JSON loads for one line comparison + assert response.json()["bytecode"] == json.loads( + '["_h", 33, 0, 33, 3, 36, 0, 15, 40, 45, 33, 1, 36, 0, 6, 37, 0, 32, "headers", 32, "x-count", 36, 0, 42, 1, 32, "body", 32, "payload", 32, "inputs", 1, 2, 32, "method", 32, "method", 32, "inputs", 1, 2, 42, 3, 32, "url", 32, "inputs", 1, 2, 2, "fetch", 2, 35, 39, -52, 35]' + ), response.json() + + @patch("posthog.permissions.posthoganalytics.feature_enabled", return_value=True) + def test_generates_inputs_bytecode(self, *args): + response = self.client.post(f"/api/projects/{self.team.id}/hog_functions/", data=EXAMPLE_FULL) + assert response.status_code == status.HTTP_201_CREATED, response.json() + assert response.json()["inputs"] == { + "url": { + "value": "http://localhost:2080/0e02d917-563f-4050-9725-aad881b69937", + "bytecode": ["_h", 32, "http://localhost:2080/0e02d917-563f-4050-9725-aad881b69937"], + }, + "payload": { + "value": { + "event": "{event}", + "groups": "{groups}", + "nested": {"foo": "{event.url}"}, + "person": "{person}", + "event_url": "{f'{event.url}-test'}", + }, + "bytecode": { + "event": ["_h", 32, "event", 1, 1], + "groups": ["_h", 32, "groups", 1, 1], + "nested": {"foo": ["_h", 32, "url", 32, "event", 1, 2]}, + "person": ["_h", 32, "person", 1, 1], + "event_url": ["_h", 32, "-test", 32, "url", 32, "event", 1, 2, 2, "concat", 2], + }, + }, + "method": {"value": "POST"}, + "headers": { + "value": {"version": "v={event.properties.$lib_version}"}, + "bytecode": { + "version": ["_h", 32, "$lib_version", 32, "properties", 32, "event", 1, 3, 32, "v=", 2, "concat", 2] + }, + }, + } + + @patch("posthog.permissions.posthoganalytics.feature_enabled", return_value=True) + def test_generates_filters_bytecode(self, *args): + action = Action.objects.create( + team=self.team, + name="test action", + steps_json=[{"event": "$pageview", "url": "docs", "url_matching": "contains"}], + ) + + self.team.test_account_filters = [ + { + "key": "email", + "value": "@posthog.com", + "operator": "not_icontains", + "type": "person", + } + ] + self.team.save() + response = self.client.post( + f"/api/projects/{self.team.id}/hog_functions/", + data={ + **EXAMPLE_FULL, + "filters": { + "events": [{"id": "$pageview", "name": "$pageview", "type": "events", "order": 0}], + "actions": [{"id": f"{action.id}", "name": "Test Action", "type": "actions", "order": 1}], + "filter_test_accounts": True, + }, + }, + ) + assert response.status_code == status.HTTP_201_CREATED, response.json() + assert response.json()["filters"] == { + "events": [{"id": "$pageview", "name": "$pageview", "type": "events", "order": 0}], + "actions": [{"id": f"{action.id}", "name": "Test Action", "type": "actions", "order": 1}], + "filter_test_accounts": True, + "bytecode": [ + "_h", + 32, + "%docs%", + 32, + "$current_url", + 32, + "properties", + 1, + 2, + 17, + 32, + "$pageview", + 32, + "event", + 1, + 1, + 11, + 3, + 2, + 32, + "%@posthog.com%", + 32, + "email", + 32, + "properties", + 32, + "person", + 1, + 3, + 20, + 3, + 2, + 32, + "$pageview", + 32, + "event", + 1, + 1, + 11, + 32, + "%@posthog.com%", + 32, + "email", + 32, + "properties", + 32, + "person", + 1, + 3, + 20, + 3, + 2, + 4, + 2, + ], + } diff --git a/posthog/migrations/0425_hogfunction.py b/posthog/migrations/0425_hogfunction.py new file mode 100644 index 00000000000000..a04b78d6d4ab91 --- /dev/null +++ b/posthog/migrations/0425_hogfunction.py @@ -0,0 +1,47 @@ +# Generated by Django 4.2.11 on 2024-06-10 08:02 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import posthog.models.utils + + +class Migration(migrations.Migration): + dependencies = [ + ("posthog", "0424_survey_current_iteration_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="HogFunction", + fields=[ + ( + "id", + models.UUIDField( + default=posthog.models.utils.UUIDT, editable=False, primary_key=True, serialize=False + ), + ), + ("name", models.CharField(blank=True, max_length=400, null=True)), + ("description", models.TextField(blank=True, default="")), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("deleted", models.BooleanField(default=False)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("enabled", models.BooleanField(default=False)), + ("hog", models.TextField()), + ("bytecode", models.JSONField(blank=True, null=True)), + ("inputs_schema", models.JSONField(null=True)), + ("inputs", models.JSONField(null=True)), + ("filters", models.JSONField(blank=True, null=True)), + ( + "created_by", + models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL + ), + ), + ("team", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="posthog.team")), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/posthog/models/__init__.py b/posthog/models/__init__.py index d0f9dcf893c809..389d573cb7e7b4 100644 --- a/posthog/models/__init__.py +++ b/posthog/models/__init__.py @@ -40,6 +40,7 @@ from .filters import Filter, RetentionFilter from .group import Group from .group_type_mapping import GroupTypeMapping +from .hog_functions import HogFunction from .insight import Insight, InsightViewed from .insight_caching_state import InsightCachingState from .instance_setting import InstanceSetting @@ -103,6 +104,7 @@ "Filter", "Group", "GroupTypeMapping", + "HogFunction", "Insight", "InsightCachingState", "InsightViewed", diff --git a/posthog/models/hog_functions/__init__.py b/posthog/models/hog_functions/__init__.py new file mode 100644 index 00000000000000..c2af1396e40791 --- /dev/null +++ b/posthog/models/hog_functions/__init__.py @@ -0,0 +1 @@ +from .hog_function import * diff --git a/posthog/models/hog_functions/hog_function.py b/posthog/models/hog_functions/hog_function.py new file mode 100644 index 00000000000000..8355832e0e2afe --- /dev/null +++ b/posthog/models/hog_functions/hog_function.py @@ -0,0 +1,118 @@ +import json +from typing import Optional + +from django.db import models +from django.db.models.signals import post_save +from django.dispatch.dispatcher import receiver + +from posthog.models.action.action import Action +from posthog.models.team.team import Team +from posthog.models.utils import UUIDModel +from posthog.redis import get_client + + +class HogFunction(UUIDModel): + team: models.ForeignKey = models.ForeignKey("Team", on_delete=models.CASCADE) + name: models.CharField = models.CharField(max_length=400, null=True, blank=True) + description: models.TextField = models.TextField(blank=True, default="") + created_at: models.DateTimeField = models.DateTimeField(auto_now_add=True, blank=True) + created_by: models.ForeignKey = models.ForeignKey("User", on_delete=models.SET_NULL, null=True, blank=True) + deleted: models.BooleanField = models.BooleanField(default=False) + updated_at: models.DateTimeField = models.DateTimeField(auto_now=True) + enabled: models.BooleanField = models.BooleanField(default=False) + + hog: models.TextField = models.TextField() + bytecode: models.JSONField = models.JSONField(null=True, blank=True) + inputs_schema: models.JSONField = models.JSONField(null=True) + inputs: models.JSONField = models.JSONField(null=True) + filters: models.JSONField = models.JSONField(null=True, blank=True) + + @property + def filter_action_ids(self) -> list[int]: + if not self.filters: + return [] + try: + return [int(action["id"]) for action in self.filters.get("actions", [])] + except KeyError: + return [] + + def compile_filters_bytecode(self, actions: Optional[dict[int, Action]] = None): + from .utils import hog_function_filters_to_expr + from posthog.hogql.bytecode import create_bytecode + + self.filters = self.filters or {} + + if actions is None: + # If not provided as an optimization we fetch all actions + actions_list = ( + Action.objects.select_related("team").filter(team_id=self.team_id).filter(id__in=self.filter_action_ids) + ) + actions = {action.id: action for action in actions_list} + + try: + self.filters["bytecode"] = create_bytecode(hog_function_filters_to_expr(self.filters, self.team, actions)) + except Exception as e: + # TODO: Better reporting of this issue + self.filters["bytecode"] = None + self.filters["bytecode_error"] = str(e) + + def save(self, *args, **kwargs): + self.compile_filters_bytecode() + return super().save(*args, **kwargs) + + def __str__(self): + return self.name + + +@receiver(post_save, sender=HogFunction) +def hog_function_saved(sender, instance: HogFunction, created, **kwargs): + get_client().publish( + "reload-hog-function", + json.dumps({"teamId": instance.team_id, "hogFunctionId": str(instance.id)}), + ) + + +@receiver(post_save, sender=Action) +def action_saved(sender, instance: Action, created, **kwargs): + # Whenever an action is saved we want to load all hog functions using it + # and trigger a refresh of the filters bytecode + + affected_hog_functions = ( + HogFunction.objects.select_related("team") + .filter(team_id=instance.team_id) + .filter(filters__contains={"actions": [{"id": str(instance.id)}]}) + ) + + refresh_hog_functions(team_id=instance.team_id, affected_hog_functions=list(affected_hog_functions)) + + +@receiver(post_save, sender=Team) +def team_saved(sender, instance: Team, created, **kwargs): + affected_hog_functions = ( + HogFunction.objects.select_related("team") + .filter(team_id=instance.id) + .filter(filters__contains={"filter_test_accounts": True}) + ) + + refresh_hog_functions(team_id=instance.id, affected_hog_functions=list(affected_hog_functions)) + + +def refresh_hog_functions(team_id: int, affected_hog_functions: list[HogFunction]) -> int: + all_related_actions = ( + Action.objects.select_related("team") + .filter(team_id=team_id) + .filter( + id__in=[ + action_id for hog_function in affected_hog_functions for action_id in hog_function.filter_action_ids + ] + ) + ) + + actions_by_id = {action.id: action for action in all_related_actions} + + for hog_function in affected_hog_functions: + hog_function.compile_filters_bytecode(actions=actions_by_id) + + updates = HogFunction.objects.bulk_update(affected_hog_functions, ["filters"]) + + return updates diff --git a/posthog/models/hog_functions/utils.py b/posthog/models/hog_functions/utils.py new file mode 100644 index 00000000000000..5ec265487d2e32 --- /dev/null +++ b/posthog/models/hog_functions/utils.py @@ -0,0 +1,66 @@ +from typing import Any +from posthog.models.action.action import Action +from posthog.hogql.bytecode import create_bytecode +from posthog.hogql.parser import parse_expr, parse_string_template +from posthog.hogql.property import action_to_expr, property_to_expr, ast +from posthog.models.team.team import Team + + +def hog_function_filters_to_expr(filters: dict, team: Team, actions: dict[int, Action]) -> ast.Expr: + test_account_filters_exprs: list[ast.Expr] = [] + if filters.get("filter_test_accounts", False): + test_account_filters_exprs = [property_to_expr(property, team) for property in team.test_account_filters] + + all_filters = filters.get("events", []) + filters.get("actions", []) + all_filters_exprs: list[ast.Expr] = [] + + if not all_filters and test_account_filters_exprs: + # Always return test filters if set and no other filters + return ast.And(exprs=test_account_filters_exprs) + + for filter in all_filters: + exprs: list[ast.Expr] = [] + exprs.extend(test_account_filters_exprs) + + # Events + if filter.get("type") == "events" and filter.get("name"): + exprs.append(parse_expr("event = {event}", {"event": ast.Constant(value=filter["name"])})) + + # Actions + if filter.get("type") == "actions": + try: + action = actions[int(filter["id"])] + exprs.append(action_to_expr(action)) + except KeyError: + # If an action doesn't exist, we want to return no events + exprs.append(parse_expr("1 = 2")) + + # Properties + if filter.get("properties"): + exprs.append(property_to_expr(filter.get("properties"), team)) + + if len(exprs) == 0: + all_filters_exprs.append(ast.Constant(value=True)) + + all_filters_exprs.append(ast.And(exprs=exprs)) + + if all_filters_exprs: + final_expr = ast.Or(exprs=all_filters_exprs) + return final_expr + else: + return ast.Constant(value=True) + + +def generate_template_bytecode(obj: Any) -> Any: + """ + Clones an object, compiling any string values to bytecode templates + """ + + if isinstance(obj, dict): + return {key: generate_template_bytecode(value) for key, value in obj.items()} + elif isinstance(obj, list): + return [generate_template_bytecode(item) for item in obj] + elif isinstance(obj, str): + return create_bytecode(parse_string_template(obj)) + else: + return obj diff --git a/posthog/models/test/test_hog_function.py b/posthog/models/test/test_hog_function.py new file mode 100644 index 00000000000000..35779b7efbad2b --- /dev/null +++ b/posthog/models/test/test_hog_function.py @@ -0,0 +1,283 @@ +import json +from django.test import TestCase +from inline_snapshot import snapshot + +from posthog.models.action.action import Action +from posthog.models.hog_functions.hog_function import HogFunction +from posthog.models.user import User +from posthog.test.base import QueryMatchingTest + + +to_dict = lambda x: json.loads(json.dumps(x)) + + +class TestHogFunction(TestCase): + def setUp(self): + super().setUp() + org, team, user = User.objects.bootstrap("Test org", "ben@posthog.com", None) + self.team = team + self.user = user + self.org = org + + def test_hog_function_basic(self): + item = HogFunction.objects.create(name="Test", team=self.team) + assert item.name == "Test" + assert item.hog == "" + assert not item.enabled + + def test_hog_function_team_no_filters_compilation(self): + item = HogFunction.objects.create(name="Test", team=self.team) + + # Some json serialization is needed to compare the bytecode more easily in tests + json_filters = to_dict(item.filters) + assert json_filters["bytecode"] == ["_h", 29] # TRUE + + def test_hog_function_filters_compilation(self): + item = HogFunction.objects.create( + name="Test", + team=self.team, + filters={ + "events": [{"id": "$pageview", "name": "$pageview", "type": "events", "order": 0}], + "actions": [{"id": "9", "name": "Test Action", "type": "actions", "order": 1}], + "filter_test_accounts": True, + }, + ) + + # Some json serialization is needed to compare the bytecode more easily in tests + json_filters = to_dict(item.filters) + + assert json_filters == { + "events": [{"id": "$pageview", "name": "$pageview", "type": "events", "order": 0}], + "actions": [{"id": "9", "name": "Test Action", "type": "actions", "order": 1}], + "filter_test_accounts": True, + "bytecode": [ + "_h", + 33, + 2, + 33, + 1, + 11, + 29, + 32, + "^(localhost|127\\.0\\.0\\.1)($|:)", + 32, + "$host", + 32, + "properties", + 1, + 2, + 2, + "toString", + 1, + 2, + "match", + 2, + 5, + 2, + "ifNull", + 2, + 3, + 2, + 32, + "$pageview", + 32, + "event", + 1, + 1, + 11, + 29, + 32, + "^(localhost|127\\.0\\.0\\.1)($|:)", + 32, + "$host", + 32, + "properties", + 1, + 2, + 2, + "toString", + 1, + 2, + "match", + 2, + 5, + 2, + "ifNull", + 2, + 3, + 2, + 4, + 2, + ], + } + + def test_hog_function_team_filters_only_compilation(self): + item = HogFunction.objects.create( + name="Test", + team=self.team, + filters={ + "filter_test_accounts": True, + }, + ) + + # Some json serialization is needed to compare the bytecode more easily in tests + json_filters = to_dict(item.filters) + + assert json.dumps(json_filters["bytecode"]) == snapshot( + '["_h", 29, 32, "^(localhost|127\\\\.0\\\\.0\\\\.1)($|:)", 32, "$host", 32, "properties", 1, 2, 2, "toString", 1, 2, "match", 2, 5, 2, "ifNull", 2, 3, 1]' + ) + + +class TestHogFunctionsBackgroundReloading(TestCase, QueryMatchingTest): + def setUp(self): + super().setUp() + org, team, user = User.objects.bootstrap("Test org", "ben@posthog.com", None) + self.team = team + self.user = user + self.org = org + + self.action = Action.objects.create( + team=self.team, + name="Test Action", + steps_json=[ + { + "event": "test-event", + "properties": [ + { + "key": "prop-1", + "operator": "exact", + "value": "old-value-1", + "type": "event", + } + ], + } + ], + ) + + self.action2 = Action.objects.create( + team=self.team, + name="Test Action", + steps_json=[ + { + "event": None, + "properties": [ + { + "key": "prop-2", + "operator": "exact", + "value": "old-value-2", + "type": "event", + } + ], + } + ], + ) + + def test_hog_functions_reload_on_action_saved(self): + hog_function_1 = HogFunction.objects.create( + name="func 1", + team=self.team, + filters={ + "actions": [ + {"id": str(self.action.id), "name": "Test Action", "type": "actions", "order": 1}, + {"id": str(self.action2.id), "name": "Test Action 2", "type": "actions", "order": 2}, + ], + }, + ) + hog_function_2 = HogFunction.objects.create( + name="func 2", + team=self.team, + filters={ + "actions": [ + {"id": str(self.action.id), "name": "Test Action", "type": "actions", "order": 1}, + ], + }, + ) + + # Check that the bytecode is correct + assert json.dumps(hog_function_1.filters["bytecode"]) == snapshot( + '["_h", 32, "old-value-2", 32, "prop-2", 32, "properties", 1, 2, 11, 3, 1, 32, "old-value-1", 32, "prop-1", 32, "properties", 1, 2, 11, 32, "test-event", 32, "event", 1, 1, 11, 3, 2, 3, 1, 4, 2]' + ) + + assert json.dumps(hog_function_2.filters["bytecode"]) == snapshot( + '["_h", 32, "old-value-1", 32, "prop-1", 32, "properties", 1, 2, 11, 32, "test-event", 32, "event", 1, 1, 11, 3, 2, 3, 1, 4, 1]' + ) + + # Modify the action and check that the bytecode is updated + self.action.steps_json = [ + { + "event": "test-event", + "properties": [ + { + "key": "prop-1", + "operator": "exact", + "value": "change-value", + "type": "event", + } + ], + } + ] + # 1 update action, 1 load hog functions, 1 load all related actions, 1 bulk update hog functions + with self.assertNumQueries(4): + self.action.save() + hog_function_1.refresh_from_db() + hog_function_2.refresh_from_db() + + assert json.dumps(hog_function_1.filters["bytecode"]) == snapshot( + '["_h", 32, "old-value-2", 32, "prop-2", 32, "properties", 1, 2, 11, 3, 1, 32, "change-value", 32, "prop-1", 32, "properties", 1, 2, 11, 32, "test-event", 32, "event", 1, 1, 11, 3, 2, 3, 1, 4, 2]' + ) + assert json.dumps(hog_function_2.filters["bytecode"]) == snapshot( + '["_h", 32, "change-value", 32, "prop-1", 32, "properties", 1, 2, 11, 32, "test-event", 32, "event", 1, 1, 11, 3, 2, 3, 1, 4, 1]' + ) + + def test_hog_functions_reload_on_team_saved(self): + self.team.test_account_filters = [] + self.team.save() + hog_function_1 = HogFunction.objects.create( + name="func 1", + team=self.team, + filters={ + "filter_test_accounts": True, + }, + ) + hog_function_2 = HogFunction.objects.create( + name="func 2", + team=self.team, + filters={ + "filter_test_accounts": True, + "events": [{"id": "$pageview", "name": "$pageview", "type": "events", "order": 0}], + }, + ) + hog_function_3 = HogFunction.objects.create( + name="func 3", + team=self.team, + filters={ + "filter_test_accounts": False, + }, + ) + + # Check that the bytecode is correct + assert json.dumps(hog_function_1.filters["bytecode"]) == snapshot('["_h", 29]') + assert json.dumps(hog_function_2.filters["bytecode"]) == snapshot( + '["_h", 32, "$pageview", 32, "event", 1, 1, 11, 3, 1, 4, 1]' + ) + assert json.dumps(hog_function_3.filters["bytecode"]) == snapshot('["_h", 29]') + + # Modify the action and check that the bytecode is updated + self.team.test_account_filters = [ + {"key": "$host", "operator": "regex", "value": "^(localhost|127\\.0\\.0\\.1)($|:)"}, + {"key": "$pageview", "operator": "regex", "value": "test"}, + ] + # 1 update team, 1 load hog functions, 1 update hog functions + with self.assertNumQueries(3): + self.team.save() + hog_function_1.refresh_from_db() + hog_function_2.refresh_from_db() + hog_function_3.refresh_from_db() + + assert json.dumps(hog_function_1.filters["bytecode"]) == snapshot( + '["_h", 30, 32, "test", 32, "$pageview", 32, "properties", 1, 2, 2, "toString", 1, 2, "match", 2, 2, "ifNull", 2, 30, 32, "^(localhost|127\\\\.0\\\\.0\\\\.1)($|:)", 32, "$host", 32, "properties", 1, 2, 2, "toString", 1, 2, "match", 2, 2, "ifNull", 2, 3, 2]' + ) + assert json.dumps(hog_function_2.filters["bytecode"]) == snapshot( + '["_h", 32, "$pageview", 32, "event", 1, 1, 11, 30, 32, "test", 32, "$pageview", 32, "properties", 1, 2, 2, "toString", 1, 2, "match", 2, 2, "ifNull", 2, 30, 32, "^(localhost|127\\\\.0\\\\.0\\\\.1)($|:)", 32, "$host", 32, "properties", 1, 2, 2, "toString", 1, 2, "match", 2, 2, "ifNull", 2, 3, 3, 4, 1]' + ) + assert json.dumps(hog_function_3.filters["bytecode"]) == snapshot('["_h", 29]') diff --git a/posthog/permissions.py b/posthog/permissions.py index db5c48d92f25be..bf4054ef1cb8d1 100644 --- a/posthog/permissions.py +++ b/posthog/permissions.py @@ -5,6 +5,7 @@ from django.core.exceptions import ImproperlyConfigured from django.db.models import Model from django.views import View +import posthoganalytics from rest_framework.exceptions import NotFound, PermissionDenied from rest_framework.permissions import SAFE_METHODS, BasePermission, IsAdminUser from rest_framework.request import Request @@ -404,3 +405,37 @@ def get_scope_object(self, request, view) -> APIScopeObjectOrNotSupported: raise ImproperlyConfigured("APIScopePermission requires the view to define the scope_object attribute.") return view.scope_object + + +class PostHogFeatureFlagPermission(BasePermission): + def has_permission(self, request, view) -> bool: + user = cast(request.user, User) + organization = get_organization_from_view(view) + flag = getattr(view, "posthog_feature_flag", None) + + config = {} + + if not flag: + raise ImproperlyConfigured( + "PostHogFeatureFlagPermission requires the view to define the posthog_feature_flag attribute." + ) + + if isinstance(flag, str): + config[flag] = ["*"] + else: + config = flag + + for required_flag, actions in config.items(): + if "*" in actions or view.action in actions: + enabled = posthoganalytics.feature_enabled( + required_flag, + str(user.distinct_id), + groups={"organization": str(organization.id)}, + group_properties={"organization": {"id": str(organization.id)}}, + only_evaluate_locally=False, + send_feature_flag_events=False, + ) + + return enabled or False + + return True diff --git a/posthog/test/test_migration_0410.py b/posthog/test/test_migration_0410.py deleted file mode 100644 index 07e33eaf756de8..00000000000000 --- a/posthog/test/test_migration_0410.py +++ /dev/null @@ -1,67 +0,0 @@ -from posthog.test.base import NonAtomicTestMigrations - -from posthog.models.action import Action -from posthog.models.action.action_step import ActionStep -from posthog.models.team import Team -from posthog.models.organization import Organization - - -class TestActionStepsJSONMigration(NonAtomicTestMigrations): - migrate_from = "0409_action_steps_json_alter_actionstep_action" - migrate_to = "0410_action_steps_population" - - CLASS_DATA_LEVEL_SETUP = False - - def setUpBeforeMigration(self, apps): - org = Organization.objects.create(name="o1") - team = Team.objects.create(name="t1", organization=org) - - # Create a lot of actions - - for i in range(1000): - # We create this with sql as it won't have the new fields - sql = f"""INSERT INTO posthog_action (name, team_id, description, created_at, updated_at, deleted, post_to_slack, slack_message_format, is_calculating, last_calculated_at) - VALUES ('action{i}', {team.pk}, '', '2022-01-01', '2022-01-01', FALSE, FALSE, '', FALSE, '2022-01-01') RETURNING id; - """ - - action = Action.objects.raw(sql)[0] - - # We create this with sql as it won't have the new fields - sql = f"""INSERT INTO posthog_actionstep (action_id, tag_name, text, text_matching, href, href_matching, selector, url, url_matching, event, properties) - VALUES ({action.pk}, 'tag1', 'text1', 'exact', 'href1', 'exact', 'selector1', 'url1', 'exact', 'event1', '{{"key1": "value1"}}') RETURNING id; - """ - - ActionStep.objects.raw(sql)[0] - - def test_migrate_action_steps(self): - apps = self.apps - if apps is None: - # obey mypy - raise Exception("apps is None") - - all_actions = Action.objects.prefetch_related("action_steps").all() - - assert len(all_actions) == 1000 - - for action in all_actions: - assert action.steps_json == [ - { - "tag_name": "tag1", - "text": "text1", - "text_matching": "exact", - "href": "href1", - "href_matching": "exact", - "selector": "selector1", - "url": "url1", - "url_matching": "exact", - "event": "event1", - "properties": {"key1": "value1"}, - } - ], action - - def tearDown(self): - # Clean up all the steps and actions - ActionStep.objects.raw("DELETE FROM posthog_actionstep") - Action.objects.raw("DELETE FROM posthog_action") - - super().tearDown() diff --git a/production.Dockerfile b/production.Dockerfile index 4f58ac88554ec8..1e3eb2d11551f0 100644 --- a/production.Dockerfile +++ b/production.Dockerfile @@ -315,4 +315,4 @@ EXPOSE 8000 EXPOSE 8001 COPY unit.json.tpl /docker-entrypoint.d/unit.json.tpl USER root -CMD ["./bin/docker"] +CMD ["./bin/docker"] \ No newline at end of file diff --git a/requirements-dev.in b/requirements-dev.in index 691e790df2b35a..35ad7044d22ddf 100644 --- a/requirements-dev.in +++ b/requirements-dev.in @@ -21,6 +21,7 @@ django-stubs==4.2.7 Faker==17.5.0 fakeredis[lua]==2.11.0 freezegun==1.2.2 +inline-snapshot==0.10.2 packaging==23.1 black~=23.9.1 boto3-stubs[s3] diff --git a/requirements-dev.txt b/requirements-dev.txt index 6a0b72290d43b9..ded32c00acb9f6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,9 @@ -# This file was autogenerated by uv via the following command: -# uv pip compile requirements-dev.in -o requirements-dev.txt +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --output-file=requirements-dev.txt requirements-dev.in +# aiohttp==3.9.3 # via # -c requirements.txt @@ -20,6 +24,8 @@ asgiref==3.7.2 # via # -c requirements.txt # django +asttokens==2.4.1 + # via inline-snapshot async-timeout==4.0.2 # via # -c requirements.txt @@ -33,10 +39,10 @@ attrs==23.2.0 # referencing black==23.9.1 # via - # -c requirements.txt # -r requirements-dev.in # datamodel-code-generator -boto3-stubs==1.34.84 + # inline-snapshot +boto3-stubs[s3]==1.34.84 # via -r requirements-dev.in botocore-stubs==1.34.84 # via boto3-stubs @@ -58,9 +64,10 @@ click==8.1.7 # via # -c requirements.txt # black + # inline-snapshot colorama==0.4.4 # via pytest-watch -coverage==5.5 +coverage[toml]==5.5 # via pytest-cov cryptography==37.0.2 # via @@ -95,9 +102,11 @@ exceptiongroup==1.2.1 # pytest execnet==2.1.1 # via pytest-xdist +executing==2.0.1 + # via inline-snapshot faker==17.5.0 # via -r requirements-dev.in -fakeredis==2.11.0 +fakeredis[lua]==2.11.0 # via -r requirements-dev.in flaky==3.7.0 # via -r requirements-dev.in @@ -122,6 +131,8 @@ inflect==5.6.2 # via datamodel-code-generator iniconfig==1.1.1 # via pytest +inline-snapshot==0.10.2 + # via -r requirements-dev.in isort==5.2.2 # via datamodel-code-generator jinja2==3.1.4 @@ -142,8 +153,12 @@ lazy-object-proxy==1.10.0 # via openapi-spec-validator lupa==1.14.1 # via fakeredis +markdown-it-py==3.0.0 + # via rich markupsafe==2.1.5 # via jinja2 +mdurl==0.1.2 + # via markdown-it-py multidict==6.0.2 # via # -c requirements.txt @@ -157,7 +172,6 @@ mypy-boto3-s3==1.34.65 # via boto3-stubs mypy-extensions==1.0.0 # via - # -c requirements.txt # -r requirements-dev.in # black # mypy @@ -178,9 +192,7 @@ parameterized==0.9.0 pathable==0.4.3 # via jsonschema-path pathspec==0.12.1 - # via - # -c requirements.txt - # black + # via black platformdirs==3.11.0 # via # -c requirements.txt @@ -195,7 +207,7 @@ pycparser==2.20 # via # -c requirements.txt # cffi -pydantic==2.5.3 +pydantic[email]==2.5.3 # via # -c requirements.txt # datamodel-code-generator @@ -203,6 +215,8 @@ pydantic-core==2.14.6 # via # -c requirements.txt # pydantic +pygments==2.18.0 + # via rich pytest==7.4.4 # via # -r requirements-dev.in @@ -267,6 +281,8 @@ responses==0.23.1 # via -r requirements-dev.in rfc3339-validator==0.1.4 # via openapi-schema-validator +rich==13.7.1 + # via inline-snapshot rpds-py==0.16.2 # via # -c requirements.txt @@ -281,6 +297,7 @@ ruff==0.4.3 six==1.16.0 # via # -c requirements.txt + # asttokens # prance # python-dateutil # rfc3339-validator @@ -294,13 +311,13 @@ sqlparse==0.4.4 # django syrupy==4.6.0 # via -r requirements-dev.in -toml==0.10.1 +toml==0.10.2 # via # coverage # datamodel-code-generator + # inline-snapshot tomli==2.0.1 # via - # -c requirements.txt # black # django-stubs # mypy @@ -336,6 +353,8 @@ types-retry==0.9.9.4 # via -r requirements-dev.in types-s3transfer==0.10.1 # via boto3-stubs +types-toml==0.10.8.20240310 + # via inline-snapshot types-tzlocal==5.1.0.1 # via -r requirements-dev.in typing-extensions==4.7.1 From 8ab8741e6311859dab31a0f8d5a53e04fc09b82b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Oberm=C3=BCller?= Date: Mon, 10 Jun 2024 15:19:20 +0200 Subject: [PATCH 05/35] chore(refactor): pythonic schema (#22573) --- bin/build-schema-python.sh | 3 +- ee/clickhouse/models/test/test_cohort.py | 40 +- .../queries/enterprise_cohort_query.py | 2 +- ee/clickhouse/queries/event_query.py | 2 +- .../queries/funnels/funnel_correlation.py | 20 +- ee/clickhouse/queries/groups_join_query.py | 4 +- ...est_session_recording_list_from_filters.py | 8 +- ...sion_recording_list_from_session_replay.py | 8 +- posthog/api/test/test_cohort.py | 4 +- posthog/api/test/test_insight.py | 12 +- posthog/api/test/test_query.py | 6 +- posthog/hogql/autocomplete.py | 8 +- posthog/hogql/database/database.py | 50 +- posthog/hogql/database/schema/persons.py | 8 +- posthog/hogql/database/schema/sessions.py | 2 +- .../database/schema/test/test_sessions.py | 2 +- .../test_person_where_clause_extractor.py | 4 +- posthog/hogql/database/test/test_database.py | 2 +- posthog/hogql/modifiers.py | 16 +- posthog/hogql/printer.py | 30 +- posthog/hogql/property.py | 80 +-- posthog/hogql/test/test_modifiers.py | 52 +- posthog/hogql/test/test_printer.py | 12 +- posthog/hogql/test/test_property.py | 20 +- posthog/hogql/transforms/property_types.py | 2 +- .../hogql/transforms/test/test_in_cohort.py | 20 +- .../hogql/transforms/test/test_lazy_tables.py | 4 +- .../hogql_queries/apply_dashboard_filters.py | 2 +- .../hogql_queries/insights/funnels/base.py | 22 +- .../hogql_queries/insights/funnels/funnel.py | 6 +- .../funnel_correlation_query_runner.py | 12 +- .../insights/funnels/funnel_query_context.py | 8 +- .../insights/funnels/funnel_strict.py | 6 +- .../insights/funnels/funnel_unordered.py | 6 +- .../insights/funnels/funnels_query_runner.py | 4 +- .../funnels/test/test_funnel_correlation.py | 58 +- .../test/test_funnel_correlations_persons.py | 10 +- .../hogql_queries/insights/funnels/utils.py | 10 +- .../insights/paths_query_runner.py | 24 +- .../insights/retention_query_runner.py | 20 +- .../test/test_lifecycle_query_runner.py | 50 +- .../insights/test/test_paginators.py | 2 +- .../test/test_stickiness_query_runner.py | 20 +- .../insights/trends/aggregation_operations.py | 4 +- .../insights/trends/breakdown.py | 2 +- .../insights/trends/breakdown_values.py | 2 +- .../hogql_queries/insights/trends/display.py | 16 +- .../test/test_aggregation_operations.py | 84 +-- .../test/test_trends_actors_query_builder.py | 42 +- .../test/test_trends_dashboard_filters.py | 44 +- .../test/test_trends_data_warehouse_query.py | 20 +- .../trends/test/test_trends_persons.py | 26 +- .../trends/test/test_trends_query_builder.py | 34 +- .../trends/test/test_trends_query_runner.py | 250 +++---- .../insights/trends/test/test_utils.py | 26 +- .../trends/trends_actors_query_builder.py | 6 +- .../insights/trends/trends_query_builder.py | 10 +- .../insights/trends/trends_query_runner.py | 14 +- .../insights/utils/test/test_entities.py | 44 +- .../legacy_compatibility/filter_to_query.py | 12 +- .../test/test_filter_to_query.py | 64 +- posthog/hogql_queries/query_runner.py | 12 +- .../test/test_actors_query_runner.py | 8 +- .../test/test_events_query_runner.py | 8 +- .../hogql_queries/test/test_query_runner.py | 4 +- .../hogql_queries/utils/query_date_range.py | 20 +- .../utils/test/test_query_date_range.py | 46 +- .../web_analytics/stats_table.py | 72 +- .../web_analytics/stats_table_legacy.py | 52 +- .../test/test_web_analytics_query_runner.py | 12 +- .../test/test_web_stats_table.py | 34 +- .../commands/compare_hogql_insights.py | 6 +- posthog/models/team/team.py | 16 +- posthog/queries/breakdown_props.py | 16 +- posthog/queries/event_query/event_query.py | 8 +- posthog/queries/foss_cohort_query.py | 24 +- posthog/queries/funnels/base.py | 12 +- posthog/queries/funnels/funnel_event_query.py | 10 +- .../groups_join_query/groups_join_query.py | 2 +- posthog/queries/paths/paths_event_query.py | 6 +- posthog/queries/retention/retention.py | 4 +- .../retention/retention_events_query.py | 24 +- .../stickiness/stickiness_event_query.py | 6 +- posthog/queries/trends/breakdown.py | 56 +- posthog/queries/trends/lifecycle.py | 8 +- posthog/queries/trends/total_volume.py | 14 +- posthog/queries/trends/trends_actors.py | 20 +- posthog/queries/trends/trends_event_query.py | 4 +- .../queries/trends/trends_event_query_base.py | 6 +- posthog/queries/trends/util.py | 4 +- posthog/queries/util.py | 4 +- posthog/schema.py | 624 +++++++++--------- posthog/schema_helpers.py | 16 +- ...sion_recording_list_from_replay_summary.py | 26 +- posthog/test/test_schema_helpers.py | 50 +- posthog/test/test_team.py | 6 +- posthog/warehouse/api/table.py | 2 +- posthog/warehouse/models/table.py | 16 +- 98 files changed, 1360 insertions(+), 1279 deletions(-) diff --git a/bin/build-schema-python.sh b/bin/build-schema-python.sh index d32c4caedfda92..7937731b55116d 100755 --- a/bin/build-schema-python.sh +++ b/bin/build-schema-python.sh @@ -9,7 +9,8 @@ datamodel-codegen \ --input frontend/src/queries/schema.json --input-file-type jsonschema \ --output posthog/schema.py --output-model-type pydantic_v2.BaseModel \ --custom-file-header "# mypy: disable-error-code=\"assignment\"" \ - --set-default-enum-member + --set-default-enum-member --capitalise-enum-members \ + --wrap-string-literal # Format schema.py ruff format posthog/schema.py diff --git a/ee/clickhouse/models/test/test_cohort.py b/ee/clickhouse/models/test/test_cohort.py index eb2956f1f8078b..8af41154c48a5f 100644 --- a/ee/clickhouse/models/test/test_cohort.py +++ b/ee/clickhouse/models/test/test_cohort.py @@ -142,9 +142,11 @@ def test_prop_cohort_basic_action(self): query, params = parse_prop_grouped_clauses( team_id=self.team.pk, property_group=filter.property_groups, - person_properties_mode=PersonPropertiesMode.USING_SUBQUERY - if self.team.person_on_events_mode == PersonsOnEventsMode.disabled - else PersonPropertiesMode.DIRECT_ON_EVENTS, + person_properties_mode=( + PersonPropertiesMode.USING_SUBQUERY + if self.team.person_on_events_mode == PersonsOnEventsMode.DISABLED + else PersonPropertiesMode.DIRECT_ON_EVENTS + ), hogql_context=filter.hogql_context, ) final_query = "SELECT uuid FROM events WHERE team_id = %(team_id)s {}".format(query) @@ -197,9 +199,11 @@ def test_prop_cohort_basic_event_days(self): query, params = parse_prop_grouped_clauses( team_id=self.team.pk, property_group=filter.property_groups, - person_properties_mode=PersonPropertiesMode.USING_SUBQUERY - if self.team.person_on_events_mode == PersonsOnEventsMode.disabled - else PersonPropertiesMode.DIRECT_ON_EVENTS, + person_properties_mode=( + PersonPropertiesMode.USING_SUBQUERY + if self.team.person_on_events_mode == PersonsOnEventsMode.DISABLED + else PersonPropertiesMode.DIRECT_ON_EVENTS + ), hogql_context=filter.hogql_context, ) final_query = "SELECT uuid FROM events WHERE team_id = %(team_id)s {}".format(query) @@ -222,9 +226,11 @@ def test_prop_cohort_basic_event_days(self): query, params = parse_prop_grouped_clauses( team_id=self.team.pk, property_group=filter.property_groups, - person_properties_mode=PersonPropertiesMode.USING_SUBQUERY - if self.team.person_on_events_mode == PersonsOnEventsMode.disabled - else PersonPropertiesMode.DIRECT_ON_EVENTS, + person_properties_mode=( + PersonPropertiesMode.USING_SUBQUERY + if self.team.person_on_events_mode == PersonsOnEventsMode.DISABLED + else PersonPropertiesMode.DIRECT_ON_EVENTS + ), hogql_context=filter.hogql_context, ) final_query = "SELECT uuid FROM events WHERE team_id = %(team_id)s {}".format(query) @@ -273,9 +279,11 @@ def test_prop_cohort_basic_action_days(self): query, params = parse_prop_grouped_clauses( team_id=self.team.pk, property_group=filter.property_groups, - person_properties_mode=PersonPropertiesMode.USING_SUBQUERY - if self.team.person_on_events_mode == PersonsOnEventsMode.disabled - else PersonPropertiesMode.DIRECT_ON_EVENTS, + person_properties_mode=( + PersonPropertiesMode.USING_SUBQUERY + if self.team.person_on_events_mode == PersonsOnEventsMode.DISABLED + else PersonPropertiesMode.DIRECT_ON_EVENTS + ), hogql_context=filter.hogql_context, ) final_query = "SELECT uuid FROM events WHERE team_id = %(team_id)s {}".format(query) @@ -294,9 +302,11 @@ def test_prop_cohort_basic_action_days(self): query, params = parse_prop_grouped_clauses( team_id=self.team.pk, property_group=filter.property_groups, - person_properties_mode=PersonPropertiesMode.USING_SUBQUERY - if self.team.person_on_events_mode == PersonsOnEventsMode.disabled - else PersonPropertiesMode.DIRECT_ON_EVENTS, + person_properties_mode=( + PersonPropertiesMode.USING_SUBQUERY + if self.team.person_on_events_mode == PersonsOnEventsMode.DISABLED + else PersonPropertiesMode.DIRECT_ON_EVENTS + ), hogql_context=filter.hogql_context, ) final_query = "SELECT uuid FROM events WHERE team_id = %(team_id)s {}".format(query) diff --git a/ee/clickhouse/queries/enterprise_cohort_query.py b/ee/clickhouse/queries/enterprise_cohort_query.py index 814b61e9a8bf5c..72b0ed9bf5e6af 100644 --- a/ee/clickhouse/queries/enterprise_cohort_query.py +++ b/ee/clickhouse/queries/enterprise_cohort_query.py @@ -319,7 +319,7 @@ def _get_sequence_query(self) -> tuple[str, dict[str, Any], str]: event_param_name = f"{self._cohort_pk}_event_ids" - if self.should_pushdown_persons and self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self.should_pushdown_persons and self._person_on_events_mode != PersonsOnEventsMode.DISABLED: person_prop_query, person_prop_params = self._get_prop_groups( self._inner_property_groups, person_properties_mode=PersonPropertiesMode.DIRECT_ON_EVENTS, diff --git a/ee/clickhouse/queries/event_query.py b/ee/clickhouse/queries/event_query.py index 0e16abc780049e..977ec53e74314f 100644 --- a/ee/clickhouse/queries/event_query.py +++ b/ee/clickhouse/queries/event_query.py @@ -37,7 +37,7 @@ def __init__( extra_event_properties: Optional[list[PropertyName]] = None, extra_person_fields: Optional[list[ColumnName]] = None, override_aggregate_users_by_distinct_id: Optional[bool] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, **kwargs, ) -> None: if extra_person_fields is None: diff --git a/ee/clickhouse/queries/funnels/funnel_correlation.py b/ee/clickhouse/queries/funnels/funnel_correlation.py index ff69e53d2e01e9..efa84347730d75 100644 --- a/ee/clickhouse/queries/funnels/funnel_correlation.py +++ b/ee/clickhouse/queries/funnels/funnel_correlation.py @@ -152,7 +152,7 @@ def __init__( def properties_to_include(self) -> list[str]: props_to_include = [] if ( - self._team.person_on_events_mode != PersonsOnEventsMode.disabled + self._team.person_on_events_mode != PersonsOnEventsMode.DISABLED and self._filter.correlation_type == FunnelCorrelationType.PROPERTIES ): # When dealing with properties, make sure funnel response comes with properties @@ -432,7 +432,7 @@ def get_properties_query(self) -> tuple[str, dict[str, Any]]: return query, params def _get_aggregation_target_join_query(self) -> str: - if self._team.person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + if self._team.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: aggregation_person_join = f""" JOIN funnel_actors as actors ON event.person_id = actors.actor_id @@ -499,7 +499,7 @@ def _get_events_join_query(self) -> str: def _get_aggregation_join_query(self): if self._filter.aggregation_group_type_index is None: - if self._team.person_on_events_mode != PersonsOnEventsMode.disabled and groups_on_events_querying_enabled(): + if self._team.person_on_events_mode != PersonsOnEventsMode.DISABLED and groups_on_events_querying_enabled(): return "", {} person_query, person_query_params = PersonQuery( @@ -519,7 +519,7 @@ def _get_aggregation_join_query(self): return GroupsJoinQuery(self._filter, self._team.pk, join_key="funnel_actors.actor_id").get_join_query() def _get_properties_prop_clause(self): - if self._team.person_on_events_mode != PersonsOnEventsMode.disabled and groups_on_events_querying_enabled(): + if self._team.person_on_events_mode != PersonsOnEventsMode.DISABLED and groups_on_events_querying_enabled(): group_properties_field = f"group{self._filter.aggregation_group_type_index}_properties" aggregation_properties_alias = ( "person_properties" if self._filter.aggregation_group_type_index is None else group_properties_field @@ -546,7 +546,7 @@ def _get_properties_prop_clause(self): param_name = f"property_name_{index}" if self._filter.aggregation_group_type_index is not None: expression, _ = get_property_string_expr( - "groups" if self._team.person_on_events_mode == PersonsOnEventsMode.disabled else "events", + "groups" if self._team.person_on_events_mode == PersonsOnEventsMode.DISABLED else "events", property_name, f"%({param_name})s", aggregation_properties_alias, @@ -554,13 +554,15 @@ def _get_properties_prop_clause(self): ) else: expression, _ = get_property_string_expr( - "person" if self._team.person_on_events_mode == PersonsOnEventsMode.disabled else "events", + "person" if self._team.person_on_events_mode == PersonsOnEventsMode.DISABLED else "events", property_name, f"%({param_name})s", aggregation_properties_alias, - materialised_table_column=aggregation_properties_alias - if self._team.person_on_events_mode != PersonsOnEventsMode.disabled - else "properties", + materialised_table_column=( + aggregation_properties_alias + if self._team.person_on_events_mode != PersonsOnEventsMode.DISABLED + else "properties" + ), ) person_property_params[param_name] = property_name person_property_expressions.append(expression) diff --git a/ee/clickhouse/queries/groups_join_query.py b/ee/clickhouse/queries/groups_join_query.py index 7a3dc46daf993b..ddb7d193d6d9bc 100644 --- a/ee/clickhouse/queries/groups_join_query.py +++ b/ee/clickhouse/queries/groups_join_query.py @@ -27,7 +27,7 @@ def __init__( team_id: int, column_optimizer: Optional[EnterpriseColumnOptimizer] = None, join_key: Optional[str] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, ) -> None: self._filter = filter self._team_id = team_id @@ -38,7 +38,7 @@ def __init__( def get_join_query(self) -> tuple[str, dict]: join_queries, params = [], {} - if self._person_on_events_mode != PersonsOnEventsMode.disabled and groups_on_events_querying_enabled(): + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED and groups_on_events_querying_enabled(): return "", {} for group_type_index in self._column_optimizer.group_types_to_query: diff --git a/ee/session_recordings/queries/test/test_session_recording_list_from_filters.py b/ee/session_recordings/queries/test/test_session_recording_list_from_filters.py index 84b378bdf59592..8de7d89abee6ba 100644 --- a/ee/session_recordings/queries/test/test_session_recording_list_from_filters.py +++ b/ee/session_recordings/queries/test/test_session_recording_list_from_filters.py @@ -64,7 +64,7 @@ def create_event( True, False, False, - PersonsOnEventsMode.person_id_no_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS, { "kperson_filter_pre__0": "rgInternal", "kpersonquery_person_filter_fin__0": "rgInternal", @@ -80,7 +80,7 @@ def create_event( False, False, False, - PersonsOnEventsMode.disabled, + PersonsOnEventsMode.DISABLED, { "kperson_filter_pre__0": "rgInternal", "kpersonquery_person_filter_fin__0": "rgInternal", @@ -96,7 +96,7 @@ def create_event( False, True, False, - PersonsOnEventsMode.person_id_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS, { "event_names": [], "event_start_time": mock.ANY, @@ -112,7 +112,7 @@ def create_event( False, True, True, - PersonsOnEventsMode.person_id_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS, { "event_end_time": mock.ANY, "event_names": [], diff --git a/ee/session_recordings/queries/test/test_session_recording_list_from_session_replay.py b/ee/session_recordings/queries/test/test_session_recording_list_from_session_replay.py index 797ac453e69e0a..b743302f896bfd 100644 --- a/ee/session_recordings/queries/test/test_session_recording_list_from_session_replay.py +++ b/ee/session_recordings/queries/test/test_session_recording_list_from_session_replay.py @@ -62,7 +62,7 @@ def create_event( True, False, False, - PersonsOnEventsMode.person_id_no_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS, { "kperson_filter_pre__0": "rgInternal", "kpersonquery_person_filter_fin__0": "rgInternal", @@ -78,7 +78,7 @@ def create_event( False, False, False, - PersonsOnEventsMode.disabled, + PersonsOnEventsMode.DISABLED, { "kperson_filter_pre__0": "rgInternal", "kpersonquery_person_filter_fin__0": "rgInternal", @@ -94,7 +94,7 @@ def create_event( False, True, False, - PersonsOnEventsMode.person_id_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS, { "event_names": [], "event_start_time": mock.ANY, @@ -110,7 +110,7 @@ def create_event( False, True, True, - PersonsOnEventsMode.person_id_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS, { "event_end_time": mock.ANY, "event_names": [], diff --git a/posthog/api/test/test_cohort.py b/posthog/api/test/test_cohort.py index 18764638625845..82740d73ed5154 100644 --- a/posthog/api/test/test_cohort.py +++ b/posthog/api/test/test_cohort.py @@ -816,7 +816,7 @@ def test_creating_update_and_calculating_with_new_cohort_query(self, patch_captu "key": "$some_prop", "value": "something", "type": "person", - "operator": PropertyOperator.exact, + "operator": PropertyOperator.EXACT, } ], }, @@ -846,7 +846,7 @@ def test_creating_update_and_calculating_with_new_cohort_query_dynamic_error(sel "key": "$some_prop", "value": "something", "type": "person", - "operator": PropertyOperator.exact, + "operator": PropertyOperator.EXACT, } ], }, diff --git a/posthog/api/test/test_insight.py b/posthog/api/test/test_insight.py index 64d4cd7791d38d..1fce31df54a8da 100644 --- a/posthog/api/test/test_insight.py +++ b/posthog/api/test/test_insight.py @@ -1145,10 +1145,10 @@ def test_insight_refreshing_legacy_conversion(self) -> None: [ [ # Property group filter, which is what's actually used these days PropertyGroupFilter( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[EventPropertyFilter(key="another", value="never_return_this", operator="is_not")], ) ], @@ -1377,10 +1377,10 @@ def test_insight_refreshing_legacy_with_background_update(self, spy_calculate_ta [ [ # Property group filter, which is what's actually used these days PropertyGroupFilter( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[EventPropertyFilter(key="another", value="never_return_this", operator="is_not")], ) ], @@ -1461,10 +1461,10 @@ def test_insight_refreshing_query_with_background_update( [ [ # Property group filter, which is what's actually used these days PropertyGroupFilter( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[EventPropertyFilter(key="another", value="never_return_this", operator="is_not")], ) ], diff --git a/posthog/api/test/test_query.py b/posthog/api/test/test_query.py index 099aebf055697f..4ec129186fcb92 100644 --- a/posthog/api/test/test_query.py +++ b/posthog/api/test/test_query.py @@ -261,7 +261,7 @@ def test_event_property_filter(self): type="event", key="key", value="test_val3", - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ) ] response = self.client.post(f"/api/projects/{self.team.id}/query/", {"query": query.dict()}).json() @@ -272,7 +272,7 @@ def test_event_property_filter(self): type="event", key="path", value="/", - operator=PropertyOperator.icontains, + operator=PropertyOperator.ICONTAINS, ) ] response = self.client.post(f"/api/projects/{self.team.id}/query/", {"query": query.dict()}).json() @@ -331,7 +331,7 @@ def test_person_property_filter(self): type="person", key="email", value="tom@posthog.com", - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ) ], ) diff --git a/posthog/hogql/autocomplete.py b/posthog/hogql/autocomplete.py index 4440368bccc1b7..0f73304061f338 100644 --- a/posthog/hogql/autocomplete.py +++ b/posthog/hogql/autocomplete.py @@ -258,7 +258,7 @@ def append_table_field_to_response(table: Table, suggestions: list[AutocompleteC extend_responses( available_functions, suggestions, - Kind.Function, + Kind.FUNCTION, insert_text=lambda key: f"{key}()", ) @@ -266,7 +266,7 @@ def append_table_field_to_response(table: Table, suggestions: list[AutocompleteC def extend_responses( keys: list[str], suggestions: list[AutocompleteCompletionItem], - kind: Kind = Kind.Variable, + kind: Kind = Kind.VARIABLE, insert_text: Optional[Callable[[str], str]] = None, details: Optional[list[str | None]] = None, ) -> None: @@ -365,7 +365,7 @@ def get_hogql_autocomplete( extend_responses( keys=table_aliases, suggestions=response.suggestions, - kind=Kind.Folder, + kind=Kind.FOLDER, details=["Table"] * len(table_aliases), ) break @@ -459,7 +459,7 @@ def get_hogql_autocomplete( extend_responses( keys=table_names, suggestions=response.suggestions, - kind=Kind.Folder, + kind=Kind.FOLDER, details=["Table"] * len(table_names), ) except Exception: diff --git a/posthog/hogql/database/database.py b/posthog/hogql/database/database.py index e7a21888e112d2..ce714239d43d7c 100644 --- a/posthog/hogql/database/database.py +++ b/posthog/hogql/database/database.py @@ -217,21 +217,21 @@ def create_hogql_database( modifiers = create_default_modifiers_for_team(team, modifiers) database = Database(timezone=team.timezone, week_start_day=team.week_start_day) - if modifiers.personsOnEventsMode == PersonsOnEventsMode.disabled: + if modifiers.personsOnEventsMode == PersonsOnEventsMode.DISABLED: # no change database.events.fields["person"] = FieldTraverser(chain=["pdi", "person"]) database.events.fields["person_id"] = FieldTraverser(chain=["pdi", "person_id"]) - elif modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + elif modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: database.events.fields["person_id"] = StringDatabaseField(name="person_id") _use_person_properties_from_events(database) - elif modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_override_properties_on_events: + elif modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: _use_person_id_from_person_overrides(database) _use_person_properties_from_events(database) database.events.fields["poe"].fields["id"] = database.events.fields["person_id"] - elif modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_override_properties_joined: + elif modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED: _use_person_id_from_person_overrides(database) database.events.fields["person"] = LazyJoin( from_field=["person_id"], @@ -509,23 +509,23 @@ def serialize_database( def constant_type_to_serialized_field_type(constant_type: ast.ConstantType) -> DatabaseSerializedFieldType | None: if isinstance(constant_type, ast.StringType): - return DatabaseSerializedFieldType.string + return DatabaseSerializedFieldType.STRING if isinstance(constant_type, ast.BooleanType): - return DatabaseSerializedFieldType.boolean + return DatabaseSerializedFieldType.BOOLEAN if isinstance(constant_type, ast.DateType): - return DatabaseSerializedFieldType.date + return DatabaseSerializedFieldType.DATE if isinstance(constant_type, ast.DateTimeType): - return DatabaseSerializedFieldType.datetime + return DatabaseSerializedFieldType.DATETIME if isinstance(constant_type, ast.UUIDType): - return DatabaseSerializedFieldType.string + return DatabaseSerializedFieldType.STRING if isinstance(constant_type, ast.ArrayType): - return DatabaseSerializedFieldType.array + return DatabaseSerializedFieldType.ARRAY if isinstance(constant_type, ast.TupleType): - return DatabaseSerializedFieldType.json + return DatabaseSerializedFieldType.JSON if isinstance(constant_type, ast.IntegerType): - return DatabaseSerializedFieldType.integer + return DatabaseSerializedFieldType.INTEGER if isinstance(constant_type, ast.FloatType): - return DatabaseSerializedFieldType.float + return DatabaseSerializedFieldType.FLOAT return None @@ -569,7 +569,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.integer, + type=DatabaseSerializedFieldType.INTEGER, schema_valid=schema_valid, ) ) @@ -578,7 +578,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.float, + type=DatabaseSerializedFieldType.FLOAT, schema_valid=schema_valid, ) ) @@ -587,7 +587,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.string, + type=DatabaseSerializedFieldType.STRING, schema_valid=schema_valid, ) ) @@ -596,7 +596,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.datetime, + type=DatabaseSerializedFieldType.DATETIME, schema_valid=schema_valid, ) ) @@ -605,7 +605,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.date, + type=DatabaseSerializedFieldType.DATE, schema_valid=schema_valid, ) ) @@ -614,7 +614,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.boolean, + type=DatabaseSerializedFieldType.BOOLEAN, schema_valid=schema_valid, ) ) @@ -623,7 +623,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.json, + type=DatabaseSerializedFieldType.JSON, schema_valid=schema_valid, ) ) @@ -632,7 +632,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.array, + type=DatabaseSerializedFieldType.ARRAY, schema_valid=schema_valid, ) ) @@ -643,7 +643,7 @@ def serialize_fields( field_type = constant_type_to_serialized_field_type(constant_type) if field_type is None: - field_type = DatabaseSerializedFieldType.expression + field_type = DatabaseSerializedFieldType.EXPRESSION field_output.append( DatabaseSchemaField( @@ -659,7 +659,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.view if is_view else DatabaseSerializedFieldType.lazy_table, + type=DatabaseSerializedFieldType.VIEW if is_view else DatabaseSerializedFieldType.LAZY_TABLE, schema_valid=schema_valid, table=field.resolve_table(context).to_printed_hogql(), fields=list(field.resolve_table(context).fields.keys()), @@ -670,7 +670,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.virtual_table, + type=DatabaseSerializedFieldType.VIRTUAL_TABLE, schema_valid=schema_valid, table=field.to_printed_hogql(), fields=list(field.fields.keys()), @@ -681,7 +681,7 @@ def serialize_fields( DatabaseSchemaField( name=field_key, hogql_value=hogql_value, - type=DatabaseSerializedFieldType.field_traverser, + type=DatabaseSerializedFieldType.FIELD_TRAVERSER, schema_valid=schema_valid, chain=field.chain, ) diff --git a/posthog/hogql/database/schema/persons.py b/posthog/hogql/database/schema/persons.py index 2af0a95381b675..54cf36645f5062 100644 --- a/posthog/hogql/database/schema/persons.py +++ b/posthog/hogql/database/schema/persons.py @@ -40,15 +40,15 @@ def select_from_persons_table(join_or_table: LazyJoinToAdd | LazyTableToAdd, context: HogQLContext, node: SelectQuery): version = context.modifiers.personsArgMaxVersion - if version == PersonsArgMaxVersion.auto: - version = PersonsArgMaxVersion.v1 + if version == PersonsArgMaxVersion.AUTO: + version = PersonsArgMaxVersion.V1 # If selecting properties, use the faster v2 query. Otherwise, v1 is faster. for field_chain in join_or_table.fields_accessed.values(): if field_chain[0] == "properties": - version = PersonsArgMaxVersion.v2 + version = PersonsArgMaxVersion.V2 break - if version == PersonsArgMaxVersion.v2: + if version == PersonsArgMaxVersion.V2: from posthog.hogql import ast from posthog.hogql.parser import parse_select diff --git a/posthog/hogql/database/schema/sessions.py b/posthog/hogql/database/schema/sessions.py index 586da5d1022068..fba4f4656b0128 100644 --- a/posthog/hogql/database/schema/sessions.py +++ b/posthog/hogql/database/schema/sessions.py @@ -198,7 +198,7 @@ def arg_max_merge_field(field_name: str) -> ast.Call: args=[aggregate_fields["$urls"]], ) - if context.modifiers.bounceRatePageViewMode == BounceRatePageViewMode.uniq_urls: + if context.modifiers.bounceRatePageViewMode == BounceRatePageViewMode.UNIQ_URLS: bounce_pageview_count = aggregate_fields["$num_uniq_urls"] else: bounce_pageview_count = aggregate_fields["$pageview_count"] diff --git a/posthog/hogql/database/schema/test/test_sessions.py b/posthog/hogql/database/schema/test/test_sessions.py index 53e13beee53c53..e17f8208b567c3 100644 --- a/posthog/hogql/database/schema/test/test_sessions.py +++ b/posthog/hogql/database/schema/test/test_sessions.py @@ -142,7 +142,7 @@ def test_persons_and_sessions_on_events(self): self.assertEqual(row1, (p1.uuid, "source1")) self.assertEqual(row2, (p2.uuid, "source2")) - @parameterized.expand([(BounceRatePageViewMode.uniq_urls,), (BounceRatePageViewMode.count_pageviews,)]) + @parameterized.expand([(BounceRatePageViewMode.UNIQ_URLS,), (BounceRatePageViewMode.COUNT_PAGEVIEWS,)]) def test_bounce_rate(self, bounceRatePageViewMode): # person with 2 different sessions _create_event( diff --git a/posthog/hogql/database/schema/util/test/test_person_where_clause_extractor.py b/posthog/hogql/database/schema/util/test/test_person_where_clause_extractor.py index 65f2be22a2c179..b58a03fe156475 100644 --- a/posthog/hogql/database/schema/util/test/test_person_where_clause_extractor.py +++ b/posthog/hogql/database/schema/util/test/test_person_where_clause_extractor.py @@ -40,8 +40,8 @@ def get_clause(self, query: str): team = self.team modifiers = create_default_modifiers_for_team(team) modifiers.optimizeJoinedFilters = True - modifiers.personsOnEventsMode = PersonsOnEventsMode.disabled - modifiers.personsArgMaxVersion = PersonsArgMaxVersion.v1 + modifiers.personsOnEventsMode = PersonsOnEventsMode.DISABLED + modifiers.personsArgMaxVersion = PersonsArgMaxVersion.V1 context = HogQLContext( team_id=team.pk, team=team, diff --git a/posthog/hogql/database/test/test_database.py b/posthog/hogql/database/test/test_database.py index 3e733b4cb22db8..cb380bce9da94b 100644 --- a/posthog/hogql/database/test/test_database.py +++ b/posthog/hogql/database/test/test_database.py @@ -474,7 +474,7 @@ def test_selecting_persons_from_events_ignores_future_persons(self): database=db, # disable PoE modifiers=create_default_modifiers_for_team( - self.team, HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.disabled) + self.team, HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.DISABLED) ), ) sql = "select person.id from events" diff --git a/posthog/hogql/modifiers.py b/posthog/hogql/modifiers.py index ce17684f47f3df..3aedc9572a4b8f 100644 --- a/posthog/hogql/modifiers.py +++ b/posthog/hogql/modifiers.py @@ -34,26 +34,26 @@ def create_default_modifiers_for_team( def set_default_modifier_values(modifiers: HogQLQueryModifiers, team: "Team"): if modifiers.personsOnEventsMode is None: - modifiers.personsOnEventsMode = team.person_on_events_mode or PersonsOnEventsMode.disabled + modifiers.personsOnEventsMode = team.person_on_events_mode or PersonsOnEventsMode.DISABLED if modifiers.personsArgMaxVersion is None: - modifiers.personsArgMaxVersion = PersonsArgMaxVersion.auto + modifiers.personsArgMaxVersion = PersonsArgMaxVersion.AUTO if modifiers.inCohortVia is None: - modifiers.inCohortVia = InCohortVia.auto + modifiers.inCohortVia = InCohortVia.AUTO - if modifiers.materializationMode is None or modifiers.materializationMode == MaterializationMode.auto: - modifiers.materializationMode = MaterializationMode.legacy_null_as_null + if modifiers.materializationMode is None or modifiers.materializationMode == MaterializationMode.AUTO: + modifiers.materializationMode = MaterializationMode.LEGACY_NULL_AS_NULL if modifiers.optimizeJoinedFilters is None: modifiers.optimizeJoinedFilters = False if modifiers.bounceRatePageViewMode is None: - modifiers.bounceRatePageViewMode = BounceRatePageViewMode.count_pageviews + modifiers.bounceRatePageViewMode = BounceRatePageViewMode.COUNT_PAGEVIEWS def set_default_in_cohort_via(modifiers: HogQLQueryModifiers) -> HogQLQueryModifiers: - if modifiers.inCohortVia is None or modifiers.inCohortVia == InCohortVia.auto: - modifiers.inCohortVia = InCohortVia.subquery + if modifiers.inCohortVia is None or modifiers.inCohortVia == InCohortVia.AUTO: + modifiers.inCohortVia = InCohortVia.SUBQUERY return modifiers diff --git a/posthog/hogql/printer.py b/posthog/hogql/printer.py index 69b6ae1ef342ae..e8ea399e724427 100644 --- a/posthog/hogql/printer.py +++ b/posthog/hogql/printer.py @@ -100,12 +100,12 @@ def prepare_ast_for_printing( context.modifiers = set_default_in_cohort_via(context.modifiers) - if context.modifiers.inCohortVia == InCohortVia.leftjoin_conjoined: + if context.modifiers.inCohortVia == InCohortVia.LEFTJOIN_CONJOINED: with context.timings.measure("resolve_in_cohorts_conjoined"): resolve_in_cohorts_conjoined(node, dialect, context, stack) with context.timings.measure("resolve_types"): node = resolve_types(node, context, dialect=dialect, scopes=[node.type for node in stack] if stack else None) - if context.modifiers.inCohortVia == InCohortVia.leftjoin: + if context.modifiers.inCohortVia == InCohortVia.LEFTJOIN: with context.timings.measure("resolve_in_cohorts"): resolve_in_cohorts(node, dialect, stack, context) if dialect == "clickhouse": @@ -573,23 +573,23 @@ def visit_compare_operation(self, node: ast.CompareOperation): value_if_one_side_is_null = True elif node.op == ast.CompareOperationOp.Gt: op = f"greater({left}, {right})" - constant_lambda = ( - lambda left_op, right_op: left_op > right_op if left_op is not None and right_op is not None else False + constant_lambda = lambda left_op, right_op: ( + left_op > right_op if left_op is not None and right_op is not None else False ) elif node.op == ast.CompareOperationOp.GtEq: op = f"greaterOrEquals({left}, {right})" - constant_lambda = ( - lambda left_op, right_op: left_op >= right_op if left_op is not None and right_op is not None else False + constant_lambda = lambda left_op, right_op: ( + left_op >= right_op if left_op is not None and right_op is not None else False ) elif node.op == ast.CompareOperationOp.Lt: op = f"less({left}, {right})" - constant_lambda = ( - lambda left_op, right_op: left_op < right_op if left_op is not None and right_op is not None else False + constant_lambda = lambda left_op, right_op: ( + left_op < right_op if left_op is not None and right_op is not None else False ) elif node.op == ast.CompareOperationOp.LtEq: op = f"lessOrEquals({left}, {right})" - constant_lambda = ( - lambda left_op, right_op: left_op <= right_op if left_op is not None and right_op is not None else False + constant_lambda = lambda left_op, right_op: ( + left_op <= right_op if left_op is not None and right_op is not None else False ) else: raise ImpossibleASTError(f"Unknown CompareOperationOp: {node.op.name}") @@ -956,7 +956,7 @@ def visit_field_type(self, type: ast.FieldType): and type.name == "properties" and type.table_type.field == "poe" ): - if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.disabled: + if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.DISABLED: field_sql = "person_properties" else: field_sql = "person_props" @@ -980,7 +980,7 @@ def visit_field_type(self, type: ast.FieldType): # :KLUDGE: Legacy person properties handling. Only used within non-HogQL queries, such as insights. if self.context.within_non_hogql_query and field_sql == "events__pdi__person.properties": - if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.disabled: + if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.DISABLED: field_sql = "person_properties" else: field_sql = "person_props" @@ -1030,7 +1030,7 @@ def visit_property_type(self, type: ast.PropertyType): or (isinstance(table, ast.VirtualTableType) and table.field == "poe") ): # :KLUDGE: Legacy person properties handling. Only used within non-HogQL queries, such as insights. - if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.disabled: + if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.DISABLED: materialized_column = self._get_materialized_column( "events", str(type.chain[0]), "person_properties" ) @@ -1041,9 +1041,9 @@ def visit_property_type(self, type: ast.PropertyType): if materialized_property_sql is not None: # TODO: rematerialize all columns to properly support empty strings and "null" string values. - if self.context.modifiers.materializationMode == MaterializationMode.legacy_null_as_string: + if self.context.modifiers.materializationMode == MaterializationMode.LEGACY_NULL_AS_STRING: materialized_property_sql = f"nullIf({materialized_property_sql}, '')" - else: # MaterializationMode.auto.legacy_null_as_null + else: # MaterializationMode AUTO or LEGACY_NULL_AS_NULL materialized_property_sql = f"nullIf(nullIf({materialized_property_sql}, ''), 'null')" if len(type.chain) == 1: diff --git a/posthog/hogql/property.py b/posthog/hogql/property.py index 056334812f8f54..652a46fee91416 100644 --- a/posthog/hogql/property.py +++ b/posthog/hogql/property.py @@ -105,8 +105,8 @@ def property_to_expr( raise NotImplementedError(f'PropertyGroup of unknown type "{property.type}"') if ( (isinstance(property, PropertyGroupFilter) or isinstance(property, PropertyGroupFilterValue)) - and property.type != FilterLogicalOperator.AND - and property.type != FilterLogicalOperator.OR + and property.type != FilterLogicalOperator.AND_ + and property.type != FilterLogicalOperator.OR_ ): raise NotImplementedError(f'PropertyGroupFilter of unknown type "{property.type}"') @@ -115,7 +115,7 @@ def property_to_expr( if len(property.values) == 1: return property_to_expr(property.values[0], team, scope) - if property.type == PropertyOperatorType.AND or property.type == FilterLogicalOperator.AND: + if property.type == PropertyOperatorType.AND or property.type == FilterLogicalOperator.AND_: return ast.And(exprs=[property_to_expr(p, team, scope) for p in property.values]) else: return ast.Or(exprs=[property_to_expr(p, team, scope) for p in property.values]) @@ -143,7 +143,7 @@ def property_to_expr( ): if (scope == "person" and property.type != "person") or (scope == "session" and property.type != "session"): raise NotImplementedError(f"The '{property.type}' property filter does not work in '{scope}' scope") - operator = cast(Optional[PropertyOperator], property.operator) or PropertyOperator.exact + operator = cast(Optional[PropertyOperator], property.operator) or PropertyOperator.EXACT value = property.value if property.type == "person" and scope != "person": @@ -195,20 +195,20 @@ def property_to_expr( for v in value ] if ( - operator == PropertyOperator.not_icontains - or operator == PropertyOperator.not_regex - or operator == PropertyOperator.is_not + operator == PropertyOperator.NOT_ICONTAINS + or operator == PropertyOperator.NOT_REGEX + or operator == PropertyOperator.IS_NOT ): return ast.And(exprs=exprs) return ast.Or(exprs=exprs) - if operator == PropertyOperator.is_set: + if operator == PropertyOperator.IS_SET: return ast.CompareOperation( op=ast.CompareOperationOp.NotEq, left=field, right=ast.Constant(value=None), ) - elif operator == PropertyOperator.is_not_set: + elif operator == PropertyOperator.IS_NOT_SET: return ast.Or( exprs=[ ast.CompareOperation( @@ -230,19 +230,19 @@ def property_to_expr( ] ) ) - elif operator == PropertyOperator.icontains: + elif operator == PropertyOperator.ICONTAINS: return ast.CompareOperation( op=ast.CompareOperationOp.ILike, left=field, right=ast.Constant(value=f"%{value}%"), ) - elif operator == PropertyOperator.not_icontains: + elif operator == PropertyOperator.NOT_ICONTAINS: return ast.CompareOperation( op=ast.CompareOperationOp.NotILike, left=field, right=ast.Constant(value=f"%{value}%"), ) - elif operator == PropertyOperator.regex: + elif operator == PropertyOperator.REGEX: return ast.Call( name="ifNull", args=[ @@ -250,7 +250,7 @@ def property_to_expr( ast.Constant(value=False), ], ) - elif operator == PropertyOperator.not_regex: + elif operator == PropertyOperator.NOT_REGEX: return ast.Call( name="ifNull", args=[ @@ -265,17 +265,17 @@ def property_to_expr( ast.Constant(value=True), ], ) - elif operator == PropertyOperator.exact or operator == PropertyOperator.is_date_exact: + elif operator == PropertyOperator.EXACT or operator == PropertyOperator.IS_DATE_EXACT: op = ast.CompareOperationOp.Eq - elif operator == PropertyOperator.is_not: + elif operator == PropertyOperator.IS_NOT: op = ast.CompareOperationOp.NotEq - elif operator == PropertyOperator.lt or operator == PropertyOperator.is_date_before: + elif operator == PropertyOperator.LT or operator == PropertyOperator.IS_DATE_BEFORE: op = ast.CompareOperationOp.Lt - elif operator == PropertyOperator.gt or operator == PropertyOperator.is_date_after: + elif operator == PropertyOperator.GT or operator == PropertyOperator.IS_DATE_AFTER: op = ast.CompareOperationOp.Gt - elif operator == PropertyOperator.lte: + elif operator == PropertyOperator.LTE: op = ast.CompareOperationOp.LtEq - elif operator == PropertyOperator.gte: + elif operator == PropertyOperator.GTE: op = ast.CompareOperationOp.GtEq else: raise NotImplementedError(f"PropertyOperator {operator} not implemented") @@ -365,7 +365,7 @@ def property_to_expr( if scope == "person": raise NotImplementedError(f"property_to_expr for scope {scope} not implemented for type '{property.type}'") value = property.value - operator = cast(Optional[PropertyOperator], property.operator) or PropertyOperator.exact + operator = cast(Optional[PropertyOperator], property.operator) or PropertyOperator.EXACT if isinstance(value, list): if len(value) == 1: value = value[0] @@ -385,20 +385,20 @@ def property_to_expr( for v in value ] if ( - operator == PropertyOperator.is_not - or operator == PropertyOperator.not_icontains - or operator == PropertyOperator.not_regex + operator == PropertyOperator.IS_NOT + or operator == PropertyOperator.NOT_ICONTAINS + or operator == PropertyOperator.NOT_REGEX ): return ast.And(exprs=exprs) return ast.Or(exprs=exprs) if property.key == "selector" or property.key == "tag_name": - if operator != PropertyOperator.exact and operator != PropertyOperator.is_not: + if operator != PropertyOperator.EXACT and operator != PropertyOperator.IS_NOT: raise NotImplementedError( f"property_to_expr for element {property.key} only supports exact and is_not operators, not {operator}" ) expr = selector_to_expr(str(value)) if property.key == "selector" else tag_name_to_expr(str(value)) - if operator == PropertyOperator.is_not: + if operator == PropertyOperator.IS_NOT: return ast.Call(name="not", args=[expr]) return expr @@ -445,19 +445,19 @@ def action_to_expr(action: Action) -> ast.Expr: exprs.append(tag_name_to_expr(step.tag_name)) if step.href is not None: if step.href_matching == "regex": - operator = PropertyOperator.regex + operator = PropertyOperator.REGEX elif step.href_matching == "contains": - operator = PropertyOperator.icontains + operator = PropertyOperator.ICONTAINS else: - operator = PropertyOperator.exact + operator = PropertyOperator.EXACT exprs.append(element_chain_key_filter("href", step.href, operator)) if step.text is not None: if step.text_matching == "regex": - operator = PropertyOperator.regex + operator = PropertyOperator.REGEX elif step.text_matching == "contains": - operator = PropertyOperator.icontains + operator = PropertyOperator.ICONTAINS else: - operator = PropertyOperator.exact + operator = PropertyOperator.EXACT exprs.append(element_chain_key_filter("text", step.text, operator)) if step.url: @@ -510,28 +510,28 @@ def entity_to_expr(entity: RetentionEntity) -> ast.Expr: def element_chain_key_filter(key: str, text: str, operator: PropertyOperator): escaped = text.replace('"', r"\"") - if operator == PropertyOperator.is_set or operator == PropertyOperator.is_not_set: + if operator == PropertyOperator.IS_SET or operator == PropertyOperator.IS_NOT_SET: value = r'[^"]+' - elif operator == PropertyOperator.icontains or operator == PropertyOperator.not_icontains: + elif operator == PropertyOperator.ICONTAINS or operator == PropertyOperator.NOT_ICONTAINS: value = rf'[^"]*{re.escape(escaped)}[^"]*' - elif operator == PropertyOperator.regex or operator == PropertyOperator.not_regex: + elif operator == PropertyOperator.REGEX or operator == PropertyOperator.NOT_REGEX: value = escaped - elif operator == PropertyOperator.exact or operator == PropertyOperator.is_not: + elif operator == PropertyOperator.EXACT or operator == PropertyOperator.IS_NOT: value = re.escape(escaped) else: raise NotImplementedError(f"element_href_to_expr not implemented for operator {operator}") regex = f'({key}="{value}")' - if operator == PropertyOperator.icontains or operator == PropertyOperator.not_icontains: + if operator == PropertyOperator.ICONTAINS or operator == PropertyOperator.NOT_ICONTAINS: expr = parse_expr("elements_chain =~* {regex}", {"regex": ast.Constant(value=str(regex))}) else: expr = parse_expr("elements_chain =~ {regex}", {"regex": ast.Constant(value=str(regex))}) if ( - operator == PropertyOperator.is_not_set - or operator == PropertyOperator.not_icontains - or operator == PropertyOperator.is_not - or operator == PropertyOperator.not_regex + operator == PropertyOperator.IS_NOT_SET + or operator == PropertyOperator.NOT_ICONTAINS + or operator == PropertyOperator.IS_NOT + or operator == PropertyOperator.NOT_REGEX ): expr = ast.Call(name="not", args=[expr]) return expr diff --git a/posthog/hogql/test/test_modifiers.py b/posthog/hogql/test/test_modifiers.py index b4619cacfbc21d..d118f4e32355dd 100644 --- a/posthog/hogql/test/test_modifiers.py +++ b/posthog/hogql/test/test_modifiers.py @@ -17,34 +17,34 @@ class TestModifiers(BaseTest): def test_create_default_modifiers_for_team_init(self): assert self.team.person_on_events_mode == "disabled" modifiers = create_default_modifiers_for_team(self.team) - assert modifiers.personsOnEventsMode == PersonsOnEventsMode.disabled # NB! not a None + assert modifiers.personsOnEventsMode == PersonsOnEventsMode.DISABLED # NB! not a None modifiers = create_default_modifiers_for_team( self.team, - HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.person_id_no_override_properties_on_events), + HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS), ) - assert modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_no_override_properties_on_events + assert modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS modifiers = create_default_modifiers_for_team( self.team, - HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.person_id_override_properties_on_events), + HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS), ) - assert modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_override_properties_on_events + assert modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS def test_team_modifiers_override(self): assert self.team.modifiers is None modifiers = create_default_modifiers_for_team(self.team) assert modifiers.personsOnEventsMode == self.team.default_modifiers["personsOnEventsMode"] - assert modifiers.personsOnEventsMode == PersonsOnEventsMode.disabled # the default mode + assert modifiers.personsOnEventsMode == PersonsOnEventsMode.DISABLED # the default mode - self.team.modifiers = {"personsOnEventsMode": PersonsOnEventsMode.person_id_override_properties_on_events} + self.team.modifiers = {"personsOnEventsMode": PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS} self.team.save() modifiers = create_default_modifiers_for_team(self.team) - assert modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_override_properties_on_events - assert self.team.default_modifiers["personsOnEventsMode"] == PersonsOnEventsMode.disabled # no change here + assert modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS + assert self.team.default_modifiers["personsOnEventsMode"] == PersonsOnEventsMode.DISABLED # no change here - self.team.modifiers = {"personsOnEventsMode": PersonsOnEventsMode.person_id_no_override_properties_on_events} + self.team.modifiers = {"personsOnEventsMode": PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS} self.team.save() modifiers = create_default_modifiers_for_team(self.team) - assert modifiers.personsOnEventsMode == PersonsOnEventsMode.person_id_no_override_properties_on_events + assert modifiers.personsOnEventsMode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS def test_modifiers_persons_on_events_mode_person_id_override_properties_on_events(self): query = "SELECT event, person_id FROM events" @@ -53,7 +53,7 @@ def test_modifiers_persons_on_events_mode_person_id_override_properties_on_event response = execute_hogql_query( query, team=self.team, - modifiers=HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.disabled), + modifiers=HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.DISABLED), ) assert " JOIN " in response.clickhouse @@ -62,7 +62,7 @@ def test_modifiers_persons_on_events_mode_person_id_override_properties_on_event query, team=self.team, modifiers=HogQLQueryModifiers( - personsOnEventsMode=PersonsOnEventsMode.person_id_no_override_properties_on_events + personsOnEventsMode=PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS ), ) assert " JOIN " not in response.clickhouse @@ -77,7 +77,7 @@ class TestCase(NamedTuple): test_cases: list[TestCase] = [ TestCase( - PersonsOnEventsMode.disabled, + PersonsOnEventsMode.DISABLED, [ "events.event AS event", "events__pdi__person.id AS id", @@ -86,7 +86,7 @@ class TestCase(NamedTuple): ], ), TestCase( - PersonsOnEventsMode.person_id_no_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS, [ "events.event AS event", "events.person_id AS id", @@ -95,7 +95,7 @@ class TestCase(NamedTuple): ], ), TestCase( - PersonsOnEventsMode.person_id_override_properties_on_events, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS, [ "events.event AS event", "if(not(empty(events__override.distinct_id)), events__override.person_id, events.person_id) AS id", @@ -107,7 +107,7 @@ class TestCase(NamedTuple): ], ), TestCase( - PersonsOnEventsMode.person_id_override_properties_joined, + PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED, [ "events.event AS event", "events__person.id AS id", @@ -142,7 +142,7 @@ def test_modifiers_persons_argmax_version_v2(self): response = execute_hogql_query( query, team=self.team, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.v1), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.V1), ) assert "in(tuple(person.id, person.version)" not in response.clickhouse @@ -150,7 +150,7 @@ def test_modifiers_persons_argmax_version_v2(self): response = execute_hogql_query( query, team=self.team, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.v2), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.V2), ) assert "in(tuple(person.id, person.version)" in response.clickhouse @@ -159,7 +159,7 @@ def test_modifiers_persons_argmax_version_auto(self): response = execute_hogql_query( "SELECT id, properties.$browser, is_identified FROM persons", team=self.team, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.auto), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.AUTO), ) assert "in(tuple(person.id, person.version)" in response.clickhouse @@ -167,7 +167,7 @@ def test_modifiers_persons_argmax_version_auto(self): response = execute_hogql_query( "SELECT id, properties FROM persons", team=self.team, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.auto), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.AUTO), ) assert "in(tuple(person.id, person.version)" in response.clickhouse @@ -175,7 +175,7 @@ def test_modifiers_persons_argmax_version_auto(self): response = execute_hogql_query( "SELECT id, is_identified FROM persons", team=self.team, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.auto), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.AUTO), ) assert "in(tuple(person.id, person.version)" not in response.clickhouse @@ -208,7 +208,7 @@ def test_modifiers_materialization_mode(self): response = execute_hogql_query( "SELECT properties.$browser FROM events", team=self.team, - modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.auto), + modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.AUTO), pretty=False, ) assert ( @@ -218,7 +218,7 @@ def test_modifiers_materialization_mode(self): response = execute_hogql_query( "SELECT properties.$browser FROM events", team=self.team, - modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.legacy_null_as_null), + modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.LEGACY_NULL_AS_NULL), pretty=False, ) assert ( @@ -228,7 +228,7 @@ def test_modifiers_materialization_mode(self): response = execute_hogql_query( "SELECT properties.$browser FROM events", team=self.team, - modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.legacy_null_as_string), + modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.LEGACY_NULL_AS_STRING), pretty=False, ) assert "SELECT nullIf(events.`mat_$browser`, '') AS `$browser` FROM events" in response.clickhouse @@ -236,7 +236,7 @@ def test_modifiers_materialization_mode(self): response = execute_hogql_query( "SELECT properties.$browser FROM events", team=self.team, - modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.disabled), + modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.DISABLED), pretty=False, ) assert ( diff --git a/posthog/hogql/test/test_printer.py b/posthog/hogql/test/test_printer.py index c8466c4f403879..52807f90d200c3 100644 --- a/posthog/hogql/test/test_printer.py +++ b/posthog/hogql/test/test_printer.py @@ -141,7 +141,7 @@ def test_fields_and_properties(self): context = HogQLContext( team_id=self.team.pk, within_non_hogql_query=True, - modifiers=HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.disabled), + modifiers=HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.DISABLED), ) self.assertEqual( self._expr("person.properties.bla", context), @@ -158,7 +158,7 @@ def test_fields_and_properties(self): team_id=self.team.pk, within_non_hogql_query=True, modifiers=HogQLQueryModifiers( - personsOnEventsMode=PersonsOnEventsMode.person_id_no_override_properties_on_events + personsOnEventsMode=PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS ), ) self.assertEqual( @@ -749,7 +749,7 @@ def test_select_sample(self): context = HogQLContext( team_id=self.team.pk, enable_select_queries=True, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.v2), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.V2), ) query = self._select( "SELECT events.event FROM events SAMPLE 2/78 OFFSET 999 JOIN persons ON persons.id=events.person_id", @@ -772,7 +772,7 @@ def test_select_sample(self): context = HogQLContext( team_id=self.team.pk, enable_select_queries=True, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.v2), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.V2), ) self.assertEqual( self._select( @@ -795,7 +795,7 @@ def test_select_sample(self): context = HogQLContext( team_id=self.team.pk, enable_select_queries=True, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.v2), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.V2), ) expected = self._select( "SELECT events.event FROM events SAMPLE 2/78 OFFSET 999 JOIN persons ON persons.id=events.person_id", @@ -814,7 +814,7 @@ def test_select_sample(self): context = HogQLContext( team_id=self.team.pk, enable_select_queries=True, - modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.v2), + modifiers=HogQLQueryModifiers(personsArgMaxVersion=PersonsArgMaxVersion.V2), ) expected = self._select( "SELECT events.event FROM events SAMPLE 2/78 OFFSET 999 JOIN persons SAMPLE 0.1 ON persons.id=events.person_id", diff --git a/posthog/hogql/test/test_property.py b/posthog/hogql/test/test_property.py index 03c42efb3673b0..7a4edb8a3db238 100644 --- a/posthog/hogql/test/test_property.py +++ b/posthog/hogql/test/test_property.py @@ -336,7 +336,7 @@ def test_property_to_expr_element(self): "operator": "exact", } ), - clear_locations(element_chain_key_filter("href", "href-text.", PropertyOperator.exact)), + clear_locations(element_chain_key_filter("href", "href-text.", PropertyOperator.EXACT)), ) self.assertEqual( self._property_to_expr( @@ -347,7 +347,7 @@ def test_property_to_expr_element(self): "operator": "regex", } ), - clear_locations(element_chain_key_filter("text", "text-text.", PropertyOperator.regex)), + clear_locations(element_chain_key_filter("text", "text-text.", PropertyOperator.REGEX)), ) def test_property_groups(self): @@ -503,35 +503,35 @@ def test_selector_to_expr(self): def test_elements_chain_key_filter(self): self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.is_set)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.IS_SET)), clear_locations(elements_chain_match('(href="[^"]+")')), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.is_not_set)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.IS_NOT_SET)), clear_locations(not_call(elements_chain_match('(href="[^"]+")'))), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.icontains)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.ICONTAINS)), clear_locations(elements_chain_imatch('(href="[^"]*boo\\.\\.[^"]*")')), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.not_icontains)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.NOT_ICONTAINS)), clear_locations(not_call(elements_chain_imatch('(href="[^"]*boo\\.\\.[^"]*")'))), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.regex)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.REGEX)), clear_locations(elements_chain_match('(href="boo..")')), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.not_regex)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.NOT_REGEX)), clear_locations(not_call(elements_chain_match('(href="boo..")'))), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.exact)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.EXACT)), clear_locations(elements_chain_match('(href="boo\\.\\.")')), ) self.assertEqual( - clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.is_not)), + clear_locations(element_chain_key_filter("href", "boo..", PropertyOperator.IS_NOT)), clear_locations(not_call(elements_chain_match('(href="boo\\.\\.")'))), ) diff --git a/posthog/hogql/transforms/property_types.py b/posthog/hogql/transforms/property_types.py index 58c8539533acdb..26ab9fc8b3656a 100644 --- a/posthog/hogql/transforms/property_types.py +++ b/posthog/hogql/transforms/property_types.py @@ -236,7 +236,7 @@ def _add_property_notice( ): property_name = str(node.chain[-1]) if property_type == "person": - if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.disabled: + if self.context.modifiers.personsOnEventsMode != PersonsOnEventsMode.DISABLED: materialized_column = self._get_materialized_column("events", property_name, "person_properties") else: materialized_column = self._get_materialized_column("person", property_name, "properties") diff --git a/posthog/hogql/transforms/test/test_in_cohort.py b/posthog/hogql/transforms/test/test_in_cohort.py index cde9e291c43c0d..bdceb43df7932a 100644 --- a/posthog/hogql/transforms/test/test_in_cohort.py +++ b/posthog/hogql/transforms/test/test_in_cohort.py @@ -48,7 +48,7 @@ def test_in_cohort_dynamic(self): response = execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT {cohort.pk} AND event='{random_uuid}'", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN), pretty=False, ) assert pretty_print_response_in_tests(response, self.team.pk) == self.snapshot # type: ignore @@ -65,7 +65,7 @@ def test_in_cohort_static(self): response = execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT {cohort.pk}", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN), pretty=False, ) assert pretty_print_response_in_tests(response, self.team.pk) == self.snapshot # type: ignore @@ -81,7 +81,7 @@ def test_in_cohort_strings(self): response = execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT 'my cohort'", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN), pretty=False, ) assert pretty_print_response_in_tests(response, self.team.pk) == self.snapshot # type: ignore @@ -93,7 +93,7 @@ def test_in_cohort_error(self): execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT true", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.subquery), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.SUBQUERY), pretty=False, ) self.assertEqual(str(e.exception), "cohort() takes exactly one string or integer argument") @@ -102,7 +102,7 @@ def test_in_cohort_error(self): execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT 'blabla'", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.subquery), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.SUBQUERY), pretty=False, ) self.assertEqual(str(e.exception), "Could not find a cohort with the name 'blabla'") @@ -118,7 +118,7 @@ def test_in_cohort_conjoined_string(self): response = execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT 'my cohort'", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin_conjoined), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN_CONJOINED), pretty=False, ) assert pretty_print_response_in_tests(response, self.team.pk) == self.snapshot # type: ignore @@ -133,7 +133,7 @@ def test_in_cohort_conjoined_int(self): response = execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT {cohort.pk}", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin_conjoined), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN_CONJOINED), pretty=False, ) assert pretty_print_response_in_tests(response, self.team.pk) == self.snapshot # type: ignore @@ -150,7 +150,7 @@ def test_in_cohort_conjoined_dynamic(self): response = execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT {cohort.pk} AND event='{random_uuid}'", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin_conjoined), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN_CONJOINED), pretty=False, ) assert pretty_print_response_in_tests(response, self.team.pk) == self.snapshot # type: ignore @@ -164,7 +164,7 @@ def test_in_cohort_conjoined_error(self): execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT true", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin_conjoined), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN_CONJOINED), pretty=False, ) self.assertEqual(str(e.exception), "cohort() takes exactly one string or integer argument") @@ -173,7 +173,7 @@ def test_in_cohort_conjoined_error(self): execute_hogql_query( f"SELECT event FROM events WHERE person_id IN COHORT 'blabla'", self.team, - modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.leftjoin_conjoined), + modifiers=HogQLQueryModifiers(inCohortVia=InCohortVia.LEFTJOIN_CONJOINED), pretty=False, ) self.assertEqual(str(e.exception), "Could not find a cohort with the name 'blabla'") diff --git a/posthog/hogql/transforms/test/test_lazy_tables.py b/posthog/hogql/transforms/test/test_lazy_tables.py index 3ea762b786983a..2b5cabb7d0d4e1 100644 --- a/posthog/hogql/transforms/test/test_lazy_tables.py +++ b/posthog/hogql/transforms/test/test_lazy_tables.py @@ -142,7 +142,7 @@ def test_resolve_lazy_table_indirectly_referenced(self): # of a lazy join. printed = self._print_select( "select person.id from events", - HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.person_id_override_properties_joined), + HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED), ) assert printed == self.snapshot @@ -152,6 +152,6 @@ def test_resolve_lazy_table_indirect_duplicate_references(self): # is referenced via two different selected columns. printed = self._print_select( "select person_id, person.properties from events", - HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.person_id_override_properties_joined), + HogQLQueryModifiers(personsOnEventsMode=PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED), ) assert printed == self.snapshot diff --git a/posthog/hogql_queries/apply_dashboard_filters.py b/posthog/hogql_queries/apply_dashboard_filters.py index 64302522586eab..6d8e74f0fb5884 100644 --- a/posthog/hogql_queries/apply_dashboard_filters.py +++ b/posthog/hogql_queries/apply_dashboard_filters.py @@ -3,7 +3,7 @@ from posthog.models import Team from posthog.schema import DashboardFilter, NodeKind -WRAPPER_NODE_KINDS = [NodeKind.DataTableNode, NodeKind.DataVisualizationNode, NodeKind.InsightVizNode] +WRAPPER_NODE_KINDS = [NodeKind.DATA_TABLE_NODE, NodeKind.DATA_VISUALIZATION_NODE, NodeKind.INSIGHT_VIZ_NODE] # Apply the filters from the django-style Dashboard object diff --git a/posthog/hogql_queries/insights/funnels/base.py b/posthog/hogql_queries/insights/funnels/base.py index 104a2aa87d580e..e673f59c467ef7 100644 --- a/posthog/hogql_queries/insights/funnels/base.py +++ b/posthog/hogql_queries/insights/funnels/base.py @@ -119,7 +119,7 @@ def _get_breakdown_select_prop(self) -> list[ast.Expr]: prop_basic = ast.Alias(alias="prop_basic", expr=self._get_breakdown_expr()) # breakdown attribution - if breakdownAttributionType == BreakdownAttributionType.step: + if breakdownAttributionType == BreakdownAttributionType.STEP: select_columns = [] default_breakdown_selector = "[]" if self._query_has_array_breakdown() else "NULL" # get prop value from each step @@ -133,8 +133,8 @@ def _get_breakdown_select_prop(self) -> list[ast.Expr]: return [prop_basic, *select_columns, final_select, prop_window] elif breakdownAttributionType in [ - BreakdownAttributionType.first_touch, - BreakdownAttributionType.last_touch, + BreakdownAttributionType.FIRST_TOUCH, + BreakdownAttributionType.LAST_TOUCH, ]: prop_conditional = ( "notEmpty(arrayFilter(x -> notEmpty(x), prop))" @@ -143,7 +143,7 @@ def _get_breakdown_select_prop(self) -> list[ast.Expr]: ) aggregate_operation = ( - "argMinIf" if breakdownAttributionType == BreakdownAttributionType.first_touch else "argMaxIf" + "argMinIf" if breakdownAttributionType == BreakdownAttributionType.FIRST_TOUCH else "argMaxIf" ) breakdown_window_selector = f"{aggregate_operation}(prop, timestamp, {prop_conditional})" @@ -368,7 +368,7 @@ def _get_inner_event_query( funnel_events_query.select = [*funnel_events_query.select, *all_step_cols] - if breakdown and breakdownType == BreakdownType.cohort: + if breakdown and breakdownType == BreakdownType.COHORT: if funnel_events_query.select_from is None: raise ValidationError("Apologies, there was an error adding cohort breakdowns to the query.") funnel_events_query.select_from.next_join = self._get_cohort_breakdown_join() @@ -378,7 +378,7 @@ def _get_inner_event_query( steps_conditions = self._get_steps_conditions(length=len(entities_to_use)) funnel_events_query.where = ast.And(exprs=[funnel_events_query.where, steps_conditions]) - if breakdown and breakdownAttributionType != BreakdownAttributionType.all_events: + if breakdown and breakdownAttributionType != BreakdownAttributionType.ALL_EVENTS: # ALL_EVENTS attribution is the old default, which doesn't need the subquery return self._add_breakdown_attribution_subquery(funnel_events_query) @@ -425,8 +425,8 @@ def _add_breakdown_attribution_subquery(self, inner_query: ast.SelectQuery) -> a ) if breakdownAttributionType in [ - BreakdownAttributionType.first_touch, - BreakdownAttributionType.last_touch, + BreakdownAttributionType.FIRST_TOUCH, + BreakdownAttributionType.LAST_TOUCH, ]: # When breaking down by first/last touch, each person can only have one prop value # so just select that. Except for the empty case, where we select the default. @@ -987,9 +987,9 @@ def _get_step_counts_query(self, outer_select: list[ast.Expr], inner_select: lis *person_and_group_properties, ] if breakdown and breakdownType in [ - BreakdownType.person, - BreakdownType.event, - BreakdownType.group, + BreakdownType.PERSON, + BreakdownType.EVENT, + BreakdownType.GROUP, ]: time_fields = [ parse_expr(f"min(step_{i}_conversion_time) as step_{i}_conversion_time") for i in range(1, max_steps) diff --git a/posthog/hogql_queries/insights/funnels/funnel.py b/posthog/hogql_queries/insights/funnels/funnel.py index c44108dbc61eab..470979d32b26d9 100644 --- a/posthog/hogql_queries/insights/funnels/funnel.py +++ b/posthog/hogql_queries/insights/funnels/funnel.py @@ -33,9 +33,9 @@ def get_query(self): max_steps = self.context.max_steps if self.context.breakdown and self.context.breakdownType in [ - BreakdownType.person, - BreakdownType.event, - BreakdownType.group, + BreakdownType.PERSON, + BreakdownType.EVENT, + BreakdownType.GROUP, ]: return self._breakdown_other_subquery() diff --git a/posthog/hogql_queries/insights/funnels/funnel_correlation_query_runner.py b/posthog/hogql_queries/insights/funnels/funnel_correlation_query_runner.py index fc6bc0cc5657c3..33026970c1e2e0 100644 --- a/posthog/hogql_queries/insights/funnels/funnel_correlation_query_runner.py +++ b/posthog/hogql_queries/insights/funnels/funnel_correlation_query_runner.py @@ -303,7 +303,7 @@ def serialize_event_odds_ratio(self, odds_ratio: EventOddsRatio) -> EventOddsRat failure_count=odds_ratio["failure_count"], odds_ratio=odds_ratio["odds_ratio"], correlation_type=( - CorrelationType.success if odds_ratio["correlation_type"] == "success" else CorrelationType.failure + CorrelationType.SUCCESS if odds_ratio["correlation_type"] == "success" else CorrelationType.FAILURE ), event=event_definition, ) @@ -334,10 +334,10 @@ def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery: Returns a query string and params, which are used to generate the contingency table. The query returns success and failure count for event / property values, along with total success and failure counts. """ - if self.query.funnelCorrelationType == FunnelCorrelationResultsType.properties: + if self.query.funnelCorrelationType == FunnelCorrelationResultsType.PROPERTIES: return self.get_properties_query() - if self.query.funnelCorrelationType == FunnelCorrelationResultsType.event_with_properties: + if self.query.funnelCorrelationType == FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES: return self.get_event_property_query() return self.get_event_query() @@ -345,7 +345,7 @@ def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery: def to_actors_query(self) -> ast.SelectQuery | ast.SelectUnionQuery: assert self.correlation_actors_query is not None - if self.query.funnelCorrelationType == FunnelCorrelationResultsType.properties: + if self.query.funnelCorrelationType == FunnelCorrelationResultsType.PROPERTIES: # Filtering on persons / groups properties can be pushed down to funnel events query if ( self.correlation_actors_query.funnelCorrelationPropertyValues @@ -841,7 +841,7 @@ def _get_funnel_step_names(self) -> list[str]: def properties_to_include(self) -> list[str]: props_to_include: list[str] = [] # TODO: implement or remove - # if self.query.funnelCorrelationType == FunnelCorrelationResultsType.properties: + # if self.query.funnelCorrelationType == FunnelCorrelationResultsType.PROPERTIES: # assert self.query.funnelCorrelationNames is not None # # When dealing with properties, make sure funnel response comes with properties @@ -859,7 +859,7 @@ def properties_to_include(self) -> list[str]: def support_autocapture_elements(self) -> bool: if ( - self.query.funnelCorrelationType == FunnelCorrelationResultsType.event_with_properties + self.query.funnelCorrelationType == FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES and AUTOCAPTURE_EVENT in (self.query.funnelCorrelationEventNames or []) ): return True diff --git a/posthog/hogql_queries/insights/funnels/funnel_query_context.py b/posthog/hogql_queries/insights/funnels/funnel_query_context.py index 499dc3eb9ed4c8..14ec8ba4d16243 100644 --- a/posthog/hogql_queries/insights/funnels/funnel_query_context.py +++ b/posthog/hogql_queries/insights/funnels/funnel_query_context.py @@ -57,15 +57,15 @@ def __init__( self.breakdownFilter = self.query.breakdownFilter or BreakdownFilter() # defaults - self.interval = self.query.interval or IntervalType.day + self.interval = self.query.interval or IntervalType.DAY - self.breakdownType = self.breakdownFilter.breakdown_type or BreakdownType.event + self.breakdownType = self.breakdownFilter.breakdown_type or BreakdownType.EVENT self.breakdownAttributionType = ( - self.funnelsFilter.breakdownAttributionType or BreakdownAttributionType.first_touch + self.funnelsFilter.breakdownAttributionType or BreakdownAttributionType.FIRST_TOUCH ) self.funnelWindowInterval = self.funnelsFilter.funnelWindowInterval or 14 self.funnelWindowIntervalUnit = ( - self.funnelsFilter.funnelWindowIntervalUnit or FunnelConversionWindowTimeUnit.day + self.funnelsFilter.funnelWindowIntervalUnit or FunnelConversionWindowTimeUnit.DAY ) self.includeTimestamp = include_timestamp diff --git a/posthog/hogql_queries/insights/funnels/funnel_strict.py b/posthog/hogql_queries/insights/funnels/funnel_strict.py index a9e60361514e58..4bf9b5ce19ea1c 100644 --- a/posthog/hogql_queries/insights/funnels/funnel_strict.py +++ b/posthog/hogql_queries/insights/funnels/funnel_strict.py @@ -9,9 +9,9 @@ def get_query(self): max_steps = self.context.max_steps if self.context.breakdown and self.context.breakdownType in [ - BreakdownType.person, - BreakdownType.event, - BreakdownType.group, + BreakdownType.PERSON, + BreakdownType.EVENT, + BreakdownType.GROUP, ]: return self._breakdown_other_subquery() diff --git a/posthog/hogql_queries/insights/funnels/funnel_unordered.py b/posthog/hogql_queries/insights/funnels/funnel_unordered.py index 3cc68af391427c..2bc3be1f8ca814 100644 --- a/posthog/hogql_queries/insights/funnels/funnel_unordered.py +++ b/posthog/hogql_queries/insights/funnels/funnel_unordered.py @@ -44,9 +44,9 @@ def get_query(self): raise ValidationError("Partial Exclusions not allowed in unordered funnels") if self.context.breakdown and self.context.breakdownType in [ - BreakdownType.person, - BreakdownType.event, - BreakdownType.group, + BreakdownType.PERSON, + BreakdownType.EVENT, + BreakdownType.GROUP, ]: return self._breakdown_other_subquery() diff --git a/posthog/hogql_queries/insights/funnels/funnels_query_runner.py b/posthog/hogql_queries/insights/funnels/funnels_query_runner.py index c6b8660132dabd..1b85d8858302ec 100644 --- a/posthog/hogql_queries/insights/funnels/funnels_query_runner.py +++ b/posthog/hogql_queries/insights/funnels/funnels_query_runner.py @@ -112,9 +112,9 @@ def funnel_order_class(self): def funnel_class(self): funnelVizType = self.context.funnelsFilter.funnelVizType - if funnelVizType == FunnelVizType.trends: + if funnelVizType == FunnelVizType.TRENDS: return FunnelTrends(context=self.context, **self.kwargs) - elif funnelVizType == FunnelVizType.time_to_convert: + elif funnelVizType == FunnelVizType.TIME_TO_CONVERT: return FunnelTimeToConvert(context=self.context) else: return self.funnel_order_class diff --git a/posthog/hogql_queries/insights/funnels/test/test_funnel_correlation.py b/posthog/hogql_queries/insights/funnels/test/test_funnel_correlation.py index bd5db4b9235a9e..75daa35aa5cc53 100644 --- a/posthog/hogql_queries/insights/funnels/test/test_funnel_correlation.py +++ b/posthog/hogql_queries/insights/funnels/test/test_funnel_correlation.py @@ -55,7 +55,7 @@ class TestClickhouseFunnelCorrelation(ClickhouseTestMixin, APIBaseTest): def _get_events_for_filters( self, filters, - funnelCorrelationType=FunnelCorrelationResultsType.events, + funnelCorrelationType=FunnelCorrelationResultsType.EVENTS, funnelCorrelationNames=None, funnelCorrelationExcludeNames=None, funnelCorrelationExcludeEventNames=None, @@ -90,10 +90,10 @@ def _get_actors_for_property( ): funnelCorrelationPropertyValues = [ ( - PersonPropertyFilter(key=prop, value=value, operator=PropertyOperator.exact) + PersonPropertyFilter(key=prop, value=value, operator=PropertyOperator.EXACT) if type == "person" else GroupPropertyFilter( - key=prop, value=value, group_type_index=group_type_index, operator=PropertyOperator.exact + key=prop, value=value, group_type_index=group_type_index, operator=PropertyOperator.EXACT ) ) for prop, value, type, group_type_index in property_values @@ -102,7 +102,7 @@ def _get_actors_for_property( serialized_actors = get_actors( filters, self.team, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=funnelCorrelationNames, funnelCorrelationPersonConverted=success, funnelCorrelationPropertyValues=funnelCorrelationPropertyValues, @@ -158,7 +158,7 @@ def test_basic_funnel_correlation_with_events(self): timestamp="2020-01-03T14:00:00Z", ) - result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.events) + result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.EVENTS) odds_ratios = [item.pop("odds_ratio") for item in result] expected_odds_ratios = [11, 1 / 11] @@ -200,7 +200,7 @@ def test_basic_funnel_correlation_with_events(self): # Now exclude positively_related result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.events, + funnelCorrelationType=FunnelCorrelationResultsType.EVENTS, funnelCorrelationExcludeEventNames=["positively_related"], ) @@ -417,7 +417,7 @@ def test_funnel_correlation_with_events_and_groups(self): "aggregation_group_type_index": 0, } - result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.events) + result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.EVENTS) odds_ratios = [item.pop("odds_ratio") for item in result] expected_odds_ratios = [12 / 7, 1 / 11] @@ -585,7 +585,7 @@ def test_basic_funnel_correlation_with_properties(self): ) result, _ = self._get_events_for_filters( - filters, funnelCorrelationType=FunnelCorrelationResultsType.properties, funnelCorrelationNames=["$browser"] + filters, funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["$browser"] ) odds_ratios = [item.pop("odds_ratio") for item in result] @@ -784,7 +784,7 @@ def test_funnel_correlation_with_properties_and_groups(self): } result, _ = self._get_events_for_filters( - filters, funnelCorrelationType=FunnelCorrelationResultsType.properties, funnelCorrelationNames=["industry"] + filters, funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["industry"] ) odds_ratios = [item.pop("odds_ratio") for item in result] @@ -852,7 +852,7 @@ def test_funnel_correlation_with_properties_and_groups(self): # test with `$all` as property # _run property correlation with filter on all properties new_result, _ = self._get_events_for_filters( - filters, funnelCorrelationType=FunnelCorrelationResultsType.properties, funnelCorrelationNames=["$all"] + filters, funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["$all"] ) odds_ratios = [item.pop("odds_ratio") for item in new_result] @@ -989,7 +989,7 @@ def test_funnel_correlation_with_properties_and_groups_person_on_events(self): with override_instance_config("PERSON_ON_EVENTS_ENABLED", True): result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["industry"], ) @@ -1055,7 +1055,7 @@ def test_funnel_correlation_with_properties_and_groups_person_on_events(self): # _run property correlation with filter on all properties new_result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["$all"], ) @@ -1193,14 +1193,14 @@ def test_correlation_with_properties_raises_validation_error(self): with self.assertRaises(ValidationError): self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, # funnelCorrelationNames=["$browser"] -- missing ) with self.assertRaises(ValidationError): self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.event_with_properties, + funnelCorrelationType=FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES, # "funnelCorrelationEventNames": ["rick"] -- missing ) @@ -1306,7 +1306,7 @@ def test_correlation_with_multiple_properties(self): result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["$browser", "$nice"], ) @@ -1377,7 +1377,7 @@ def test_correlation_with_multiple_properties(self): # _run property correlation with filter on all properties new_result, _ = self._get_events_for_filters( - filters, funnelCorrelationType=FunnelCorrelationResultsType.properties, funnelCorrelationNames=["$all"] + filters, funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["$all"] ) odds_ratios = [item.pop("odds_ratio") for item in new_result] @@ -1396,7 +1396,7 @@ def test_correlation_with_multiple_properties(self): # search for $all but exclude $browser new_result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationNames=["$all"], funnelCorrelationExcludeNames=["$browser"], ) @@ -1506,7 +1506,7 @@ def test_discarding_insignificant_events(self): # Discard both due to % FunnelCorrelationQueryRunner.MIN_PERSON_PERCENTAGE = 0.11 FunnelCorrelationQueryRunner.MIN_PERSON_COUNT = 25 - result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.events) + result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.EVENTS) self.assertEqual(len(result), 2) @@ -1557,7 +1557,7 @@ def test_events_within_conversion_window_for_correlation(self): timestamp="2020-01-02T14:15:00Z", # event happened outside conversion window ) - result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.events) + result, _ = self._get_events_for_filters(filters, funnelCorrelationType=FunnelCorrelationResultsType.EVENTS) odds_ratios = [item.pop("odds_ratio") for item in result] expected_odds_ratios = [4] @@ -1637,7 +1637,7 @@ def test_funnel_correlation_with_event_properties(self): result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.event_with_properties, + funnelCorrelationType=FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES, funnelCorrelationEventNames=[ "positively_related", "negatively_related", @@ -1682,7 +1682,7 @@ def test_funnel_correlation_with_event_properties(self): self._get_actors_for_event( filters, "positively_related", - [EventPropertyFilter(operator=PropertyOperator.exact, key="blah", value="value_bleh")], + [EventPropertyFilter(operator=PropertyOperator.EXACT, key="blah", value="value_bleh")], ) ), 5, @@ -1692,7 +1692,7 @@ def test_funnel_correlation_with_event_properties(self): self._get_actors_for_event( filters, "positively_related", - [EventPropertyFilter(operator=PropertyOperator.exact, key="signup_source", value="facebook")], + [EventPropertyFilter(operator=PropertyOperator.EXACT, key="signup_source", value="facebook")], ) ), 3, @@ -1702,7 +1702,7 @@ def test_funnel_correlation_with_event_properties(self): self._get_actors_for_event( filters, "positively_related", - [EventPropertyFilter(operator=PropertyOperator.exact, key="signup_source", value="facebook")], + [EventPropertyFilter(operator=PropertyOperator.EXACT, key="signup_source", value="facebook")], False, ) ), @@ -1713,7 +1713,7 @@ def test_funnel_correlation_with_event_properties(self): self._get_actors_for_event( filters, "negatively_related", - [EventPropertyFilter(operator=PropertyOperator.exact, key="signup_source", value="email")], + [EventPropertyFilter(operator=PropertyOperator.EXACT, key="signup_source", value="email")], False, ) ), @@ -1803,7 +1803,7 @@ def test_funnel_correlation_with_event_properties_and_groups(self): result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.event_with_properties, + funnelCorrelationType=FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES, funnelCorrelationEventNames=[ "positively_related", "negatively_related", @@ -1888,7 +1888,7 @@ def test_funnel_correlation_with_event_properties_exclusions(self): result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.event_with_properties, + funnelCorrelationType=FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES, funnelCorrelationEventNames=["positively_related"], funnelCorrelationEventExcludePropertyNames=["signup_source"], ) @@ -1912,7 +1912,7 @@ def test_funnel_correlation_with_event_properties_exclusions(self): self._get_actors_for_event( filters, "positively_related", - [EventPropertyFilter(operator=PropertyOperator.exact, key="blah", value="value_bleh")], + [EventPropertyFilter(operator=PropertyOperator.EXACT, key="blah", value="value_bleh")], ) ), 3, @@ -1924,7 +1924,7 @@ def test_funnel_correlation_with_event_properties_exclusions(self): self._get_actors_for_event( filters, "positively_related", - [EventPropertyFilter(operator=PropertyOperator.exact, key="signup_source", value="facebook")], + [EventPropertyFilter(operator=PropertyOperator.EXACT, key="signup_source", value="facebook")], ) ), 3, @@ -1996,7 +1996,7 @@ def test_funnel_correlation_with_event_properties_autocapture(self): result, _ = self._get_events_for_filters( filters, - funnelCorrelationType=FunnelCorrelationResultsType.event_with_properties, + funnelCorrelationType=FunnelCorrelationResultsType.EVENT_WITH_PROPERTIES, funnelCorrelationEventNames=["$autocapture"], ) diff --git a/posthog/hogql_queries/insights/funnels/test/test_funnel_correlations_persons.py b/posthog/hogql_queries/insights/funnels/test/test_funnel_correlations_persons.py index 02f65a468e4eb9..a6694deed83304 100644 --- a/posthog/hogql_queries/insights/funnels/test/test_funnel_correlations_persons.py +++ b/posthog/hogql_queries/insights/funnels/test/test_funnel_correlations_persons.py @@ -39,7 +39,7 @@ def get_actors( filters: dict[str, Any], team: Team, - funnelCorrelationType: Optional[FunnelCorrelationResultsType] = FunnelCorrelationResultsType.events, + funnelCorrelationType: Optional[FunnelCorrelationResultsType] = FunnelCorrelationResultsType.EVENTS, funnelCorrelationNames=None, funnelCorrelationPersonConverted: Optional[bool] = None, funnelCorrelationPersonEntity: Optional[EventsNode] = None, @@ -50,7 +50,7 @@ def get_actors( funnel_actors_query = FunnelsActorsQuery(source=funnels_query, includeRecordings=includeRecordings) correlation_query = FunnelCorrelationQuery( source=funnel_actors_query, - funnelCorrelationType=(funnelCorrelationType or FunnelCorrelationResultsType.events), + funnelCorrelationType=(funnelCorrelationType or FunnelCorrelationResultsType.EVENTS), funnelCorrelationNames=funnelCorrelationNames, # funnelCorrelationExcludeNames=funnelCorrelationExcludeNames, # funnelCorrelationExcludeEventNames=funnelCorrelationExcludeEventNames, @@ -466,7 +466,7 @@ def test_funnel_correlation_on_properties_with_recordings(self): results = get_actors( filters, self.team, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationPersonConverted=True, funnelCorrelationPropertyValues=[ { @@ -579,7 +579,7 @@ def test_strict_funnel_correlation_with_recordings(self): results = get_actors( filters, self.team, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationPersonConverted=True, funnelCorrelationPropertyValues=[ { @@ -613,7 +613,7 @@ def test_strict_funnel_correlation_with_recordings(self): results = get_actors( filters, self.team, - funnelCorrelationType=FunnelCorrelationResultsType.properties, + funnelCorrelationType=FunnelCorrelationResultsType.PROPERTIES, funnelCorrelationPersonConverted=False, funnelCorrelationPropertyValues=[ { diff --git a/posthog/hogql_queries/insights/funnels/utils.py b/posthog/hogql_queries/insights/funnels/utils.py index 6da16512bdceb4..d5c968a9134945 100644 --- a/posthog/hogql_queries/insights/funnels/utils.py +++ b/posthog/hogql_queries/insights/funnels/utils.py @@ -12,9 +12,9 @@ def get_funnel_order_class(funnelsFilter: FunnelsFilter): FunnelUnordered, ) - if funnelsFilter.funnelOrderType == StepOrderValue.unordered: + if funnelsFilter.funnelOrderType == StepOrderValue.UNORDERED: return FunnelUnordered - elif funnelsFilter.funnelOrderType == StepOrderValue.strict: + elif funnelsFilter.funnelOrderType == StepOrderValue.STRICT: return FunnelStrict return Funnel @@ -27,12 +27,12 @@ def get_funnel_actor_class(funnelsFilter: FunnelsFilter): FunnelTrendsActors, ) - if funnelsFilter.funnelVizType == FunnelVizType.trends: + if funnelsFilter.funnelVizType == FunnelVizType.TRENDS: return FunnelTrendsActors else: - if funnelsFilter.funnelOrderType == StepOrderValue.unordered: + if funnelsFilter.funnelOrderType == StepOrderValue.UNORDERED: return FunnelUnorderedActors - elif funnelsFilter.funnelOrderType == StepOrderValue.strict: + elif funnelsFilter.funnelOrderType == StepOrderValue.STRICT: return FunnelStrictActors else: return FunnelActors diff --git a/posthog/hogql_queries/insights/paths_query_runner.py b/posthog/hogql_queries/insights/paths_query_runner.py index a0c3ba92e5e955..30185e28f8923b 100644 --- a/posthog/hogql_queries/insights/paths_query_runner.py +++ b/posthog/hogql_queries/insights/paths_query_runner.py @@ -82,13 +82,13 @@ def _get_event_query(self) -> list[ast.Expr]: if not self.query.pathsFilter.includeEventTypes: return [] - if PathType.field_pageview in self.query.pathsFilter.includeEventTypes: + if PathType.FIELD_PAGEVIEW in self.query.pathsFilter.includeEventTypes: or_conditions.append(parse_expr("event = {event}", {"event": ast.Constant(value=PAGEVIEW_EVENT)})) - if PathType.field_screen in self.query.pathsFilter.includeEventTypes: + if PathType.FIELD_SCREEN in self.query.pathsFilter.includeEventTypes: or_conditions.append(parse_expr("event = {event}", {"event": ast.Constant(value=SCREEN_EVENT)})) - if PathType.custom_event in self.query.pathsFilter.includeEventTypes: + if PathType.CUSTOM_EVENT in self.query.pathsFilter.includeEventTypes: or_conditions.append(parse_expr("NOT startsWith(events.event, '$')")) if or_conditions: @@ -146,8 +146,8 @@ def handle_funnel(self) -> tuple[list, Optional[ast.Expr]]: funnelSourceFilter = funnelSource.funnelsFilter or FunnelsFilter() if funnelPathType in ( - FunnelPathType.funnel_path_after_step, - FunnelPathType.funnel_path_before_step, + FunnelPathType.FUNNEL_PATH_AFTER_STEP, + FunnelPathType.FUNNEL_PATH_BEFORE_STEP, ): funnel_fields = [ ast.Alias(alias="target_timestamp", expr=ast.Field(chain=["funnel_actors", "timestamp"])), @@ -155,15 +155,15 @@ def handle_funnel(self) -> tuple[list, Optional[ast.Expr]]: interval = funnelSourceFilter.funnelWindowInterval or 14 unit = funnelSourceFilter.funnelWindowIntervalUnit interval_unit = funnel_window_interval_unit_to_sql(unit) - operator = ">=" if funnelPathType == FunnelPathType.funnel_path_after_step else "<=" + operator = ">=" if funnelPathType == FunnelPathType.FUNNEL_PATH_AFTER_STEP else "<=" default_case = f"events.timestamp {operator} toTimeZone({{target_timestamp}}, 'UTC')" - if funnelPathType == FunnelPathType.funnel_path_after_step and funnelStep and funnelStep < 0: + if funnelPathType == FunnelPathType.FUNNEL_PATH_AFTER_STEP and funnelStep and funnelStep < 0: default_case += f" + INTERVAL {interval} {interval_unit}" event_filter = parse_expr( default_case, {"target_timestamp": ast.Field(chain=["funnel_actors", "timestamp"])} ) return funnel_fields, event_filter - elif funnelPathType == FunnelPathType.funnel_path_between_steps: + elif funnelPathType == FunnelPathType.FUNNEL_PATH_BETWEEN_STEPS: funnel_fields = [ ast.Alias(alias="min_timestamp", expr=ast.Field(chain=["funnel_actors", "min_timestamp"])), ast.Alias(alias="max_timestamp", expr=ast.Field(chain=["funnel_actors", "max_timestamp"])), @@ -210,11 +210,11 @@ def funnel_join(self) -> ast.JoinExpr: assert isinstance(actors_query_runner.source_runner, FunnelsQueryRunner) assert actors_query_runner.source_runner.context is not None actors_query_runner.source_runner.context.includeTimestamp = funnelPathType in ( - FunnelPathType.funnel_path_after_step, - FunnelPathType.funnel_path_before_step, + FunnelPathType.FUNNEL_PATH_AFTER_STEP, + FunnelPathType.FUNNEL_PATH_BEFORE_STEP, ) actors_query_runner.source_runner.context.includePrecedingTimestamp = ( - funnelPathType == FunnelPathType.funnel_path_between_steps + funnelPathType == FunnelPathType.FUNNEL_PATH_BETWEEN_STEPS ) actors_query = actors_query_runner.to_query() @@ -530,7 +530,7 @@ def get_session_threshold_clause(self) -> ast.Expr: funnelSourceFilter = self.query.funnelPathsFilter.funnelSource.funnelsFilter or FunnelsFilter() interval = 14 - interval_unit = FunnelConversionWindowTimeUnit.day + interval_unit = FunnelConversionWindowTimeUnit.DAY if funnelSourceFilter.funnelWindowInterval: interval = funnelSourceFilter.funnelWindowInterval diff --git a/posthog/hogql_queries/insights/retention_query_runner.py b/posthog/hogql_queries/insights/retention_query_runner.py index c069c475262a22..7bbd39e6c14ff3 100644 --- a/posthog/hogql_queries/insights/retention_query_runner.py +++ b/posthog/hogql_queries/insights/retention_query_runner.py @@ -86,7 +86,7 @@ def filter_timestamp(self) -> ast.Expr: ) def _get_events_for_entity(self, entity: RetentionEntity) -> list[str | None]: - if entity.type == EntityType.actions and entity.id: + if entity.type == EntityType.ACTIONS and entity.id: action = Action.objects.get(pk=int(entity.id)) return action.get_step_events() return [entity.id] if isinstance(entity.id, str) else [None] @@ -131,7 +131,7 @@ def actor_query(self, breakdown_values_filter: Optional[int] = None) -> ast.Sele event_query_type = ( RetentionQueryType.TARGET_FIRST_TIME - if self.query.retentionFilter.retentionType == RetentionType.retention_first_time + if self.query.retentionFilter.retentionType == RetentionType.RETENTION_FIRST_TIME else RetentionQueryType.TARGET ) @@ -290,13 +290,15 @@ def actor_query(self, breakdown_values_filter: Optional[int] = None) -> ast.Sele select_from=ast.JoinExpr(table=ast.Field(chain=["events"])), where=ast.And(exprs=event_filters), group_by=[ast.Field(chain=["actor_id"])], - having=ast.CompareOperation( - op=ast.CompareOperationOp.Eq, - left=ast.Field(chain=["breakdown_values"]), - right=ast.Constant(value=breakdown_values_filter), - ) - if breakdown_values_filter is not None - else None, + having=( + ast.CompareOperation( + op=ast.CompareOperationOp.Eq, + left=ast.Field(chain=["breakdown_values"]), + right=ast.Constant(value=breakdown_values_filter), + ) + if breakdown_values_filter is not None + else None + ), ) if self.query.samplingFactor is not None and isinstance(self.query.samplingFactor, float): inner_query.select_from.sample = ast.SampleExpr( diff --git a/posthog/hogql_queries/insights/test/test_lifecycle_query_runner.py b/posthog/hogql_queries/insights/test/test_lifecycle_query_runner.py index 4818966fbf2eee..258f4bed968590 100644 --- a/posthog/hogql_queries/insights/test/test_lifecycle_query_runner.py +++ b/posthog/hogql_queries/insights/test/test_lifecycle_query_runner.py @@ -106,7 +106,7 @@ def test_lifecycle_query_group_0(self): date_from = "2020-01-09" date_to = "2020-01-19" - response = self._run_lifecycle_query(date_from, date_to, IntervalType.day, 0) + response = self._run_lifecycle_query(date_from, date_to, IntervalType.DAY, 0) statuses = [res["status"] for res in response.results] self.assertEqual(["new", "returning", "resurrecting", "dormant"], statuses) @@ -357,7 +357,7 @@ def test_lifecycle_query_group_1(self): date_from = "2020-01-09" date_to = "2020-01-19" - response = self._run_lifecycle_query(date_from, date_to, IntervalType.day, 1) + response = self._run_lifecycle_query(date_from, date_to, IntervalType.DAY, 1) statuses = [res["status"] for res in response.results] self.assertEqual(["new", "returning", "resurrecting", "dormant"], statuses) @@ -670,7 +670,7 @@ def test_lifecycle_query_whole_range(self): date_from = "2020-01-09" date_to = "2020-01-19" - response = self._run_lifecycle_query(date_from, date_to, IntervalType.day) + response = self._run_lifecycle_query(date_from, date_to, IntervalType.DAY) statuses = [res["status"] for res in response.results] self.assertEqual(["new", "returning", "resurrecting", "dormant"], statuses) @@ -891,7 +891,7 @@ def test_events_query_whole_range(self): date_from = "2020-01-09" date_to = "2020-01-19" - response = self._run_events_query(date_from, date_to, IntervalType.day) + response = self._run_events_query(date_from, date_to, IntervalType.DAY) self.assertEqual( { @@ -919,7 +919,7 @@ def test_events_query_partial_range(self): self._create_test_events() date_from = "2020-01-12" date_to = "2020-01-14" - response = self._run_events_query(date_from, date_to, IntervalType.day) + response = self._run_events_query(date_from, date_to, IntervalType.DAY) self.assertEqual( { @@ -959,7 +959,7 @@ def test_lifecycle_trend(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], ), ) @@ -1009,7 +1009,7 @@ def test_lifecycle_trend_all_events(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event=None)], ), ) @@ -1073,7 +1073,7 @@ def test_lifecycle_trend_with_zero_person_ids(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], ), ) @@ -1174,9 +1174,9 @@ def test_lifecycle_trend_prop_filtering(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], - properties=[EventPropertyFilter(key="$number", value="1", operator=PropertyOperator.exact)], + properties=[EventPropertyFilter(key="$number", value="1", operator=PropertyOperator.EXACT)], ), ) .calculate() @@ -1199,11 +1199,11 @@ def test_lifecycle_trend_prop_filtering(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[ EventsNode( event="$pageview", - properties=[EventPropertyFilter(key="$number", value="1", operator=PropertyOperator.exact)], + properties=[EventPropertyFilter(key="$number", value="1", operator=PropertyOperator.EXACT)], ) ], ), @@ -1305,11 +1305,11 @@ def test_lifecycle_trend_person_prop_filtering(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[ EventsNode( event="$pageview", - properties=[PersonPropertyFilter(key="name", value="p1", operator=PropertyOperator.exact)], + properties=[PersonPropertyFilter(key="name", value="p1", operator=PropertyOperator.EXACT)], ) ], ), @@ -1374,7 +1374,7 @@ def test_lifecycle_trends_distinct_id_repeat(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], ), ) @@ -1419,7 +1419,7 @@ def test_lifecycle_trend_action(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[ActionsNode(id=pageview_action.pk)], ), ) @@ -1463,7 +1463,7 @@ def test_lifecycle_trend_all_time(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="all"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], ), ) @@ -1510,7 +1510,7 @@ def test_lifecycle_trend_weeks_sunday(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-02-05T00:00:00Z", date_to="2020-03-09T00:00:00Z"), - interval=IntervalType.week, + interval=IntervalType.WEEK, series=[EventsNode(event="$pageview")], ), ) @@ -1569,7 +1569,7 @@ def test_lifecycle_trend_weeks_monday(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-02-05T00:00:00Z", date_to="2020-03-09T00:00:00Z"), - interval=IntervalType.week, + interval=IntervalType.WEEK, series=[EventsNode(event="$pageview")], ), ) @@ -1624,7 +1624,7 @@ def test_lifecycle_trend_months(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-02-01T00:00:00Z", date_to="2020-09-01T00:00:00Z"), - interval=IntervalType.month, + interval=IntervalType.MONTH, series=[EventsNode(event="$pageview")], ), ) @@ -1667,7 +1667,7 @@ def test_filter_test_accounts(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], filterTestAccounts=True, ), @@ -1712,7 +1712,7 @@ def test_timezones(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12", date_to="2020-01-19"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], ), ) @@ -1738,7 +1738,7 @@ def test_timezones(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12", date_to="2020-01-19"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], ), ) @@ -1785,7 +1785,7 @@ def test_sampling(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], samplingFactor=0.1, ), @@ -1832,7 +1832,7 @@ def test_cohort_filter(self): team=self.team, query=LifecycleQuery( dateRange=InsightDateRange(date_from="2020-01-12T00:00:00Z", date_to="2020-01-19T00:00:00Z"), - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], properties=[CohortPropertyFilter(value=cohort.pk)], ), diff --git a/posthog/hogql_queries/insights/test/test_paginators.py b/posthog/hogql_queries/insights/test/test_paginators.py index d76d6ff2fcf016..06108117bd95a2 100644 --- a/posthog/hogql_queries/insights/test/test_paginators.py +++ b/posthog/hogql_queries/insights/test/test_paginators.py @@ -112,7 +112,7 @@ def test_empty_result_set(self): select=["properties.email"], limit=10, properties=[ - PersonPropertyFilter(key="email", value="random", operator=PropertyOperator.exact), + PersonPropertyFilter(key="email", value="random", operator=PropertyOperator.EXACT), ], ) ) diff --git a/posthog/hogql_queries/insights/test/test_stickiness_query_runner.py b/posthog/hogql_queries/insights/test/test_stickiness_query_runner.py index e9dace1280091f..46d1e24842f258 100644 --- a/posthog/hogql_queries/insights/test/test_stickiness_query_runner.py +++ b/posthog/hogql_queries/insights/test/test_stickiness_query_runner.py @@ -205,7 +205,7 @@ def _run_query( query_series: list[EventsNode | ActionsNode] = [EventsNode(event="$pageview")] if series is None else series query_date_from = date_from or self.default_date_from query_date_to = None if date_to == "now" else date_to or self.default_date_to - query_interval = interval or IntervalType.day + query_interval = interval or IntervalType.DAY query = StickinessQuery( series=query_series, @@ -276,7 +276,7 @@ def test_labels(self): def test_interval_hour(self): self._create_test_events() - response = self._run_query(interval=IntervalType.hour, date_from="2020-01-11", date_to="2020-01-12") + response = self._run_query(interval=IntervalType.HOUR, date_from="2020-01-11", date_to="2020-01-12") result = response.results[0] @@ -293,7 +293,7 @@ def test_interval_hour_last_days(self): self._create_test_events() with freeze_time("2020-01-20T12:00:00Z"): - response = self._run_query(interval=IntervalType.hour, date_from="-2d", date_to="now") + response = self._run_query(interval=IntervalType.HOUR, date_from="-2d", date_to="now") result = response.results[0] # 61 = 48 + 12 + 1 hours_labels = [f"{hour + 1} hour{'' if hour == 0 else 's'}" for hour in range(61)] @@ -309,7 +309,7 @@ def test_interval_hour_last_days(self): def test_interval_day(self): self._create_test_events() - response = self._run_query(interval=IntervalType.day) + response = self._run_query(interval=IntervalType.DAY) result = response.results[0] @@ -343,7 +343,7 @@ def test_interval_day(self): def test_interval_week(self): self._create_test_events() - response = self._run_query(interval=IntervalType.week) + response = self._run_query(interval=IntervalType.WEEK) result = response.results[0] @@ -356,7 +356,7 @@ def test_interval_full_weeks(self): self._create_test_events() with freeze_time("2020-01-23T12:00:00Z"): - response = self._run_query(interval=IntervalType.week, date_from="-30d", date_to="now") + response = self._run_query(interval=IntervalType.WEEK, date_from="-30d", date_to="now") result = response.results[0] @@ -368,7 +368,7 @@ def test_interval_full_weeks(self): def test_interval_month(self): self._create_test_events() - response = self._run_query(interval=IntervalType.month) + response = self._run_query(interval=IntervalType.MONTH) result = response.results[0] @@ -381,7 +381,7 @@ def test_property_filtering(self): self._create_test_events() response = self._run_query( - properties=[EventPropertyFilter(key="$browser", operator=PropertyOperator.exact, value="Chrome")] + properties=[EventPropertyFilter(key="$browser", operator=PropertyOperator.EXACT, value="Chrome")] ) result = response.results[0] @@ -425,7 +425,7 @@ def test_event_filtering(self): series: list[EventsNode | ActionsNode] = [ EventsNode( event="$pageview", - properties=[EventPropertyFilter(key="$browser", operator=PropertyOperator.exact, value="Chrome")], + properties=[EventPropertyFilter(key="$browser", operator=PropertyOperator.EXACT, value="Chrome")], ) ] @@ -545,7 +545,7 @@ def test_group_aggregations(self): self._create_test_events() series: list[EventsNode | ActionsNode] = [ - EventsNode(event="$pageview", math="unique_group", math_group_type_index=MathGroupTypeIndex.number_0) + EventsNode(event="$pageview", math="unique_group", math_group_type_index=MathGroupTypeIndex.NUMBER_0) ] response = self._run_query(series=series) diff --git a/posthog/hogql_queries/insights/trends/aggregation_operations.py b/posthog/hogql_queries/insights/trends/aggregation_operations.py index f339faf8238654..075599cb9beecc 100644 --- a/posthog/hogql_queries/insights/trends/aggregation_operations.py +++ b/posthog/hogql_queries/insights/trends/aggregation_operations.py @@ -326,8 +326,8 @@ def _events_query( ) -> ast.SelectQuery | ast.SelectUnionQuery: date_from_with_lookback = "{date_from} - {inclusive_lookback}" if self.chart_display_type in NON_TIME_SERIES_DISPLAY_TYPES and self.series.math in ( - BaseMathType.weekly_active, - BaseMathType.monthly_active, + BaseMathType.WEEKLY_ACTIVE, + BaseMathType.MONTHLY_ACTIVE, ): # TRICKY: On total value (non-time-series) insights, WAU/MAU math is simply meaningless. # There's no intuitive way to define the semantics of such a combination, so what we do is just turn it diff --git a/posthog/hogql_queries/insights/trends/breakdown.py b/posthog/hogql_queries/insights/trends/breakdown.py index b62a157bfc24d5..49491429cf54fe 100644 --- a/posthog/hogql_queries/insights/trends/breakdown.py +++ b/posthog/hogql_queries/insights/trends/breakdown.py @@ -81,7 +81,7 @@ def column_expr(self) -> ast.Alias: return ast.Alias(alias="breakdown_value", expr=self._get_breakdown_histogram_multi_if()) if self.query.breakdownFilter.breakdown_type == "cohort": - if self.modifiers.inCohortVia == InCohortVia.leftjoin_conjoined: + if self.modifiers.inCohortVia == InCohortVia.LEFTJOIN_CONJOINED: return ast.Alias( alias="breakdown_value", expr=hogql_to_string(ast.Field(chain=["__in_cohort", "cohort_id"])), diff --git a/posthog/hogql_queries/insights/trends/breakdown_values.py b/posthog/hogql_queries/insights/trends/breakdown_values.py index cc04637d5e6ecb..aee02dd9ccefb8 100644 --- a/posthog/hogql_queries/insights/trends/breakdown_values.py +++ b/posthog/hogql_queries/insights/trends/breakdown_values.py @@ -113,7 +113,7 @@ def get_breakdown_values(self) -> list[str | int]: select_field.expr = ast.Call(name="toString", args=[select_field.expr]) - if self.chart_display_type == ChartDisplayType.WorldMap: + if self.chart_display_type == ChartDisplayType.WORLD_MAP: breakdown_limit = BREAKDOWN_VALUES_LIMIT_FOR_COUNTRIES else: breakdown_limit = int(self.breakdown_limit) diff --git a/posthog/hogql_queries/insights/trends/display.py b/posthog/hogql_queries/insights/trends/display.py index de0cd18f8e79a3..f0d2cb2d125be3 100644 --- a/posthog/hogql_queries/insights/trends/display.py +++ b/posthog/hogql_queries/insights/trends/display.py @@ -10,26 +10,26 @@ def __init__(self, display_type: ChartDisplayType | None) -> None: if display_type: self.display_type = display_type else: - self.display_type = ChartDisplayType.ActionsLineGraph + self.display_type = ChartDisplayType.ACTIONS_LINE_GRAPH # No time range def is_total_value(self) -> bool: return ( - self.display_type == ChartDisplayType.BoldNumber - or self.display_type == ChartDisplayType.ActionsPie - or self.display_type == ChartDisplayType.ActionsBarValue - or self.display_type == ChartDisplayType.WorldMap - or self.display_type == ChartDisplayType.ActionsTable + self.display_type == ChartDisplayType.BOLD_NUMBER + or self.display_type == ChartDisplayType.ACTIONS_PIE + or self.display_type == ChartDisplayType.ACTIONS_BAR_VALUE + or self.display_type == ChartDisplayType.WORLD_MAP + or self.display_type == ChartDisplayType.ACTIONS_TABLE ) def wrap_inner_query(self, inner_query: ast.SelectQuery, breakdown_enabled: bool) -> ast.SelectQuery: - if self.display_type == ChartDisplayType.ActionsLineGraphCumulative: + if self.display_type == ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE: return self._get_cumulative_query(inner_query, breakdown_enabled) return inner_query def should_wrap_inner_query(self) -> bool: - return self.display_type == ChartDisplayType.ActionsLineGraphCumulative + return self.display_type == ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE def _build_aggregate_dates(self, dates_queries: ast.SelectUnionQuery) -> ast.Expr: return parse_select( diff --git a/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py b/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py index 6dfb40247f3644..2195ca2a344f5c 100644 --- a/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py +++ b/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py @@ -64,26 +64,26 @@ def test_replace_select_from(self): @pytest.mark.parametrize( "math,math_property", [ - [BaseMathType.total, None], - [BaseMathType.dau, None], - [BaseMathType.weekly_active, None], - [BaseMathType.monthly_active, None], - [BaseMathType.unique_session, None], - [PropertyMathType.avg, "$browser"], - [PropertyMathType.sum, "$browser"], - [PropertyMathType.min, "$browser"], - [PropertyMathType.max, "$browser"], - [PropertyMathType.median, "$browser"], - [PropertyMathType.p90, "$browser"], - [PropertyMathType.p95, "$browser"], - [PropertyMathType.p99, "$browser"], - [CountPerActorMathType.avg_count_per_actor, None], - [CountPerActorMathType.min_count_per_actor, None], - [CountPerActorMathType.max_count_per_actor, None], - [CountPerActorMathType.median_count_per_actor, None], - [CountPerActorMathType.p90_count_per_actor, None], - [CountPerActorMathType.p95_count_per_actor, None], - [CountPerActorMathType.p99_count_per_actor, None], + [BaseMathType.TOTAL, None], + [BaseMathType.DAU, None], + [BaseMathType.WEEKLY_ACTIVE, None], + [BaseMathType.MONTHLY_ACTIVE, None], + [BaseMathType.UNIQUE_SESSION, None], + [PropertyMathType.AVG, "$browser"], + [PropertyMathType.SUM, "$browser"], + [PropertyMathType.MIN, "$browser"], + [PropertyMathType.MAX, "$browser"], + [PropertyMathType.MEDIAN, "$browser"], + [PropertyMathType.P90, "$browser"], + [PropertyMathType.P95, "$browser"], + [PropertyMathType.P99, "$browser"], + [CountPerActorMathType.AVG_COUNT_PER_ACTOR, None], + [CountPerActorMathType.MIN_COUNT_PER_ACTOR, None], + [CountPerActorMathType.MAX_COUNT_PER_ACTOR, None], + [CountPerActorMathType.MEDIAN_COUNT_PER_ACTOR, None], + [CountPerActorMathType.P90_COUNT_PER_ACTOR, None], + [CountPerActorMathType.P95_COUNT_PER_ACTOR, None], + [CountPerActorMathType.P99_COUNT_PER_ACTOR, None], ["hogql", None], ], ) @@ -102,7 +102,7 @@ def test_all_cases_return( series = EventsNode(event="$pageview", math=math, math_property=math_property) query_date_range = QueryDateRange(date_range=None, interval=None, now=datetime.now(), team=team) - agg_ops = AggregationOperations(team, series, ChartDisplayType.ActionsLineGraph, query_date_range, False) + agg_ops = AggregationOperations(team, series, ChartDisplayType.ACTIONS_LINE_GRAPH, query_date_range, False) res = agg_ops.select_aggregation() assert isinstance(res, ast.Expr) @@ -110,26 +110,26 @@ def test_all_cases_return( @pytest.mark.parametrize( "math,result", [ - [BaseMathType.total, False], - [BaseMathType.dau, False], - [BaseMathType.weekly_active, True], - [BaseMathType.monthly_active, True], - [BaseMathType.unique_session, False], - [PropertyMathType.avg, False], - [PropertyMathType.sum, False], - [PropertyMathType.min, False], - [PropertyMathType.max, False], - [PropertyMathType.median, False], - [PropertyMathType.p90, False], - [PropertyMathType.p95, False], - [PropertyMathType.p99, False], - [CountPerActorMathType.avg_count_per_actor, True], - [CountPerActorMathType.min_count_per_actor, True], - [CountPerActorMathType.max_count_per_actor, True], - [CountPerActorMathType.median_count_per_actor, True], - [CountPerActorMathType.p90_count_per_actor, True], - [CountPerActorMathType.p95_count_per_actor, True], - [CountPerActorMathType.p99_count_per_actor, True], + [BaseMathType.TOTAL, False], + [BaseMathType.DAU, False], + [BaseMathType.WEEKLY_ACTIVE, True], + [BaseMathType.MONTHLY_ACTIVE, True], + [BaseMathType.UNIQUE_SESSION, False], + [PropertyMathType.AVG, False], + [PropertyMathType.SUM, False], + [PropertyMathType.MIN, False], + [PropertyMathType.MAX, False], + [PropertyMathType.MEDIAN, False], + [PropertyMathType.P90, False], + [PropertyMathType.P95, False], + [PropertyMathType.P99, False], + [CountPerActorMathType.AVG_COUNT_PER_ACTOR, True], + [CountPerActorMathType.MIN_COUNT_PER_ACTOR, True], + [CountPerActorMathType.MAX_COUNT_PER_ACTOR, True], + [CountPerActorMathType.MEDIAN_COUNT_PER_ACTOR, True], + [CountPerActorMathType.P90_COUNT_PER_ACTOR, True], + [CountPerActorMathType.P95_COUNT_PER_ACTOR, True], + [CountPerActorMathType.P99_COUNT_PER_ACTOR, True], ["hogql", False], ], ) @@ -147,6 +147,6 @@ def test_requiring_query_orchestration( series = EventsNode(event="$pageview", math=math) query_date_range = QueryDateRange(date_range=None, interval=None, now=datetime.now(), team=team) - agg_ops = AggregationOperations(team, series, ChartDisplayType.ActionsLineGraph, query_date_range, False) + agg_ops = AggregationOperations(team, series, ChartDisplayType.ACTIONS_LINE_GRAPH, query_date_range, False) res = agg_ops.requires_query_orchestration() assert res == result diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_actors_query_builder.py b/posthog/hogql_queries/insights/trends/test/test_trends_actors_query_builder.py index 91d5c95a77f3c8..ab9761f4454410 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_actors_query_builder.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_actors_query_builder.py @@ -102,7 +102,7 @@ def test_date_range_with_timezone(self): def test_date_range_hourly(self): self.team.timezone = "Europe/Berlin" - trends_query = default_query.model_copy(update={"interval": IntervalType.hour}, deep=True) + trends_query = default_query.model_copy(update={"interval": IntervalType.HOUR}, deep=True) self.assertEqual( self._get_date_where_sql(trends_query=trends_query, time_frame="2023-05-08T15:00:00"), @@ -114,12 +114,12 @@ def test_date_range_compare_previous(self): trends_query = default_query.model_copy(update={"trendsFilter": TrendsFilter(compare=True)}, deep=True) self.assertEqual( - self._get_date_where_sql(trends_query=trends_query, time_frame="2023-05-10", compare_value=Compare.current), + self._get_date_where_sql(trends_query=trends_query, time_frame="2023-05-10", compare_value=Compare.CURRENT), "greaterOrEquals(timestamp, toDateTime('2023-05-09 22:00:00.000000')), less(timestamp, toDateTime('2023-05-10 22:00:00.000000'))", ) self.assertEqual( self._get_date_where_sql( - trends_query=trends_query, time_frame="2023-05-10", compare_value=Compare.previous + trends_query=trends_query, time_frame="2023-05-10", compare_value=Compare.PREVIOUS ), "greaterOrEquals(timestamp, toDateTime('2023-05-02 22:00:00.000000')), less(timestamp, toDateTime('2023-05-03 22:00:00.000000'))", ) @@ -127,17 +127,17 @@ def test_date_range_compare_previous(self): def test_date_range_compare_previous_hourly(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( - update={"trendsFilter": TrendsFilter(compare=True), "interval": IntervalType.hour}, deep=True + update={"trendsFilter": TrendsFilter(compare=True), "interval": IntervalType.HOUR}, deep=True ) self.assertEqual( self._get_date_where_sql( - trends_query=trends_query, time_frame="2023-05-10T15:00:00", compare_value=Compare.current + trends_query=trends_query, time_frame="2023-05-10T15:00:00", compare_value=Compare.CURRENT ), "greaterOrEquals(timestamp, toDateTime('2023-05-10 13:00:00.000000')), less(timestamp, toDateTime('2023-05-10 14:00:00.000000'))", ) self.assertEqual( self._get_date_where_sql( - trends_query=trends_query, time_frame="2023-05-10T15:00:00", compare_value=Compare.previous + trends_query=trends_query, time_frame="2023-05-10T15:00:00", compare_value=Compare.PREVIOUS ), "greaterOrEquals(timestamp, toDateTime('2023-05-03 13:00:00.000000')), less(timestamp, toDateTime('2023-05-03 14:00:00.000000'))", ) @@ -145,7 +145,7 @@ def test_date_range_compare_previous_hourly(self): def test_date_range_total_value(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( - update={"trendsFilter": TrendsFilter(display=ChartDisplayType.BoldNumber)}, deep=True + update={"trendsFilter": TrendsFilter(display=ChartDisplayType.BOLD_NUMBER)}, deep=True ) with freeze_time("2022-06-15T12:00:00.000Z"): @@ -157,23 +157,23 @@ def test_date_range_total_value(self): def test_date_range_total_value_compare_previous(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( - update={"trendsFilter": TrendsFilter(display=ChartDisplayType.BoldNumber, compare=True)}, deep=True + update={"trendsFilter": TrendsFilter(display=ChartDisplayType.BOLD_NUMBER, compare=True)}, deep=True ) with freeze_time("2022-06-15T12:00:00.000Z"): self.assertEqual( - self._get_date_where_sql(trends_query=trends_query, compare_value=Compare.current), + self._get_date_where_sql(trends_query=trends_query, compare_value=Compare.CURRENT), "greaterOrEquals(timestamp, toDateTime('2022-06-07 22:00:00.000000')), lessOrEquals(timestamp, toDateTime('2022-06-15 21:59:59.999999'))", ) self.assertEqual( - self._get_date_where_sql(trends_query=trends_query, compare_value=Compare.previous), + self._get_date_where_sql(trends_query=trends_query, compare_value=Compare.PREVIOUS), "greaterOrEquals(timestamp, toDateTime('2022-05-31 22:00:00.000000')), lessOrEquals(timestamp, toDateTime('2022-06-08 21:59:59.999999'))", ) def test_date_range_weekly_active_users_math(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( - update={"series": [EventsNode(event="$pageview", math=BaseMathType.weekly_active)]}, deep=True + update={"series": [EventsNode(event="$pageview", math=BaseMathType.WEEKLY_ACTIVE)]}, deep=True ) with freeze_time("2024-05-30T12:00:00.000Z"): @@ -186,7 +186,7 @@ def test_date_range_weekly_active_users_math_compare_previous(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( update={ - "series": [EventsNode(event="$pageview", math=BaseMathType.weekly_active)], + "series": [EventsNode(event="$pageview", math=BaseMathType.WEEKLY_ACTIVE)], "trendsFilter": TrendsFilter(compare=True), }, deep=True, @@ -195,13 +195,13 @@ def test_date_range_weekly_active_users_math_compare_previous(self): with freeze_time("2024-05-30T12:00:00.000Z"): self.assertEqual( self._get_date_where_sql( - trends_query=trends_query, time_frame="2024-05-27", compare_value=Compare.current + trends_query=trends_query, time_frame="2024-05-27", compare_value=Compare.CURRENT ), "greaterOrEquals(timestamp, minus(toDateTime('2024-05-26 22:00:00.000000'), toIntervalDay(6))), less(timestamp, toDateTime('2024-05-27 22:00:00.000000'))", ) self.assertEqual( self._get_date_where_sql( - trends_query=trends_query, time_frame="2024-05-27", compare_value=Compare.previous + trends_query=trends_query, time_frame="2024-05-27", compare_value=Compare.PREVIOUS ), "greaterOrEquals(timestamp, minus(toDateTime('2024-05-19 22:00:00.000000'), toIntervalDay(6))), less(timestamp, toDateTime('2024-05-20 22:00:00.000000'))", ) @@ -210,8 +210,8 @@ def test_date_range_weekly_active_users_math_total_value(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( update={ - "series": [EventsNode(event="$pageview", math=BaseMathType.weekly_active)], - "trendsFilter": TrendsFilter(display=ChartDisplayType.BoldNumber), + "series": [EventsNode(event="$pageview", math=BaseMathType.WEEKLY_ACTIVE)], + "trendsFilter": TrendsFilter(display=ChartDisplayType.BOLD_NUMBER), }, deep=True, ) @@ -226,22 +226,22 @@ def test_date_range_weekly_active_users_math_total_value_compare_previous(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( update={ - "series": [EventsNode(event="$pageview", math=BaseMathType.weekly_active)], - "trendsFilter": TrendsFilter(compare=True, display=ChartDisplayType.BoldNumber), + "series": [EventsNode(event="$pageview", math=BaseMathType.WEEKLY_ACTIVE)], + "trendsFilter": TrendsFilter(compare=True, display=ChartDisplayType.BOLD_NUMBER), }, deep=True, ) with freeze_time("2024-05-30T12:00:00.000Z"): self.assertEqual( - self._get_date_where_sql(trends_query=trends_query, compare_value=Compare.previous), + self._get_date_where_sql(trends_query=trends_query, compare_value=Compare.PREVIOUS), "greaterOrEquals(timestamp, minus(toDateTime('2024-05-23 21:59:59.999999'), toIntervalDay(6))), lessOrEquals(timestamp, toDateTime('2024-05-23 21:59:59.999999'))", ) def test_date_range_monthly_active_users_math(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( - update={"series": [EventsNode(event="$pageview", math=BaseMathType.monthly_active)]}, deep=True + update={"series": [EventsNode(event="$pageview", math=BaseMathType.MONTHLY_ACTIVE)]}, deep=True ) with freeze_time("2024-05-30T12:00:00.000Z"): @@ -284,7 +284,7 @@ def test_date_range_explicit_monthly_active_users_math(self): self.team.timezone = "Europe/Berlin" trends_query = default_query.model_copy( update={ - "series": [EventsNode(event="$pageview", math=BaseMathType.monthly_active)], + "series": [EventsNode(event="$pageview", math=BaseMathType.MONTHLY_ACTIVE)], "dateRange": InsightDateRange( date_from="2024-05-08T14:29:13.634000Z", date_to="2024-05-08T14:32:57.692000Z", explicitDate=True ), diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_dashboard_filters.py b/posthog/hogql_queries/insights/trends/test/test_trends_dashboard_filters.py index b14e5fad7bd4c6..db0879c188fac9 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_dashboard_filters.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_dashboard_filters.py @@ -52,7 +52,7 @@ def test_empty_dashboard_filters_change_nothing(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, ) @@ -75,7 +75,7 @@ def test_date_from_override_updates_whole_date_range(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, ) @@ -98,7 +98,7 @@ def test_date_from_and_date_to_override_updates_whole_date_range(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, ) @@ -121,7 +121,7 @@ def test_properties_set_when_no_filters_present(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, ) @@ -146,7 +146,7 @@ def test_properties_list_extends_filters_list(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, properties=[EventPropertyFilter(key="abc", value="foo", operator="exact")], ) @@ -165,16 +165,16 @@ def test_properties_list_extends_filters_list(self): assert query_runner.query.dateRange.date_from == "2020-01-09" assert query_runner.query.dateRange.date_to == "2020-01-20" assert query_runner.query.properties == PropertyGroupFilter( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ EventPropertyFilter(key="abc", value="foo", operator="exact"), ], ), PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ EventPropertyFilter(key="xyz", value="bar", operator="regex"), ], @@ -188,17 +188,17 @@ def test_properties_list_extends_filters_group(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, properties=PropertyGroupFilter( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[EventPropertyFilter(key="abc", value="foo", operator="exact")], ), PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[EventPropertyFilter(key="klm", value="foo", operator="exact")], ), ], @@ -209,14 +209,14 @@ def test_properties_list_extends_filters_group(self): assert query_runner.query.dateRange.date_from == "2020-01-09" assert query_runner.query.dateRange.date_to == "2020-01-20" assert query_runner.query.properties == PropertyGroupFilter( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[EventPropertyFilter(key="abc", value="foo", operator="exact")], ), PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[EventPropertyFilter(key="klm", value="foo", operator="exact")], ), ], @@ -235,23 +235,23 @@ def test_properties_list_extends_filters_group(self): assert query_runner.query.dateRange.date_from == "2020-01-09" assert query_runner.query.dateRange.date_to == "2020-01-20" assert query_runner.query.properties == PropertyGroupFilter( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[ PropertyGroupFilterValue( - type=FilterLogicalOperator.OR, + type=FilterLogicalOperator.OR_, values=[EventPropertyFilter(key="abc", value="foo", operator="exact")], ), PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[EventPropertyFilter(key="klm", value="foo", operator="exact")], ), ], ), PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[EventPropertyFilter(key="xyz", value="bar", operator="regex")], ), ], @@ -263,7 +263,7 @@ def test_breakdown_limit_is_removed_when_too_large_for_dashboard(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, None, breakdown=BreakdownFilter(breakdown="abc", breakdown_limit=5), ) @@ -296,7 +296,7 @@ def test_compare_is_removed_for_all_time_range(self): query_runner = self._create_query_runner( "2024-07-07", "2024-07-14", - IntervalType.day, + IntervalType.DAY, None, trends_filters=TrendsFilter(compare=True), ) diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_data_warehouse_query.py b/posthog/hogql_queries/insights/trends/test/test_trends_data_warehouse_query.py index a606d560cf1307..e05045c28ff51e 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_data_warehouse_query.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_data_warehouse_query.py @@ -241,7 +241,7 @@ def test_trends_breakdown(self): timestamp_field="created", ) ], - breakdownFilter=BreakdownFilter(breakdown_type=BreakdownType.data_warehouse, breakdown="prop_1"), + breakdownFilter=BreakdownFilter(breakdown_type=BreakdownType.DATA_WAREHOUSE, breakdown="prop_1"), ) with freeze_time("2023-01-07"): @@ -279,7 +279,7 @@ def test_trends_breakdown_with_property(self): ) ], properties=clean_entity_properties([{"key": "prop_1", "value": "a", "type": "data_warehouse"}]), - breakdownFilter=BreakdownFilter(breakdown_type=BreakdownType.data_warehouse, breakdown="prop_1"), + breakdownFilter=BreakdownFilter(breakdown_type=BreakdownType.DATA_WAREHOUSE, breakdown="prop_1"), ) with freeze_time("2023-01-07"): @@ -317,11 +317,11 @@ def assert_column_names_with_display_type(self, display_type: ChartDisplayType): assert set(response.columns).issubset({"date", "total"}) def test_column_names_with_display_type(self): - self.assert_column_names_with_display_type(ChartDisplayType.ActionsAreaGraph) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsBar) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsBarValue) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsLineGraph) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsPie) - self.assert_column_names_with_display_type(ChartDisplayType.BoldNumber) - self.assert_column_names_with_display_type(ChartDisplayType.WorldMap) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsLineGraphCumulative) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_AREA_GRAPH) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_BAR) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_BAR_VALUE) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_LINE_GRAPH) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_PIE) + self.assert_column_names_with_display_type(ChartDisplayType.BOLD_NUMBER) + self.assert_column_names_with_display_type(ChartDisplayType.WORLD_MAP) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE) diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_persons.py b/posthog/hogql_queries/insights/trends/test/test_trends_persons.py index 27ac8f3b2da6bf..34cde171dfa4fb 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_persons.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_persons.py @@ -277,7 +277,7 @@ def test_trends_person_breakdown_persons(self): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - breakdownFilter=BreakdownFilter(breakdown="$geoip_country_code", breakdown_type=BreakdownType.person), + breakdownFilter=BreakdownFilter(breakdown="$geoip_country_code", breakdown_type=BreakdownType.PERSON), ) result = self._get_actors(trends_query=source_query, day="2023-05-01", breakdown="DE") @@ -338,7 +338,7 @@ def test_trends_breakdown_hogql_persons(self): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - breakdownFilter=BreakdownFilter(breakdown="properties.some_property", breakdown_type=BreakdownType.hogql), + breakdownFilter=BreakdownFilter(breakdown="properties.some_property", breakdown_type=BreakdownType.HOGQL), ) result = self._get_actors(trends_query=source_query, day="2023-05-01", breakdown=20) @@ -361,7 +361,7 @@ def test_trends_cohort_breakdown_persons(self): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - breakdownFilter=BreakdownFilter(breakdown=[cohort.pk], breakdown_type=BreakdownType.cohort), + breakdownFilter=BreakdownFilter(breakdown=[cohort.pk], breakdown_type=BreakdownType.COHORT), ) result = self._get_actors(trends_query=source_query, day="2023-05-01", breakdown=cohort.pk) @@ -387,7 +387,7 @@ def test_trends_multi_cohort_breakdown_persons(self): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - breakdownFilter=BreakdownFilter(breakdown=[cohort1.pk, cohort2.pk], breakdown_type=BreakdownType.cohort), + breakdownFilter=BreakdownFilter(breakdown=[cohort1.pk, cohort2.pk], breakdown_type=BreakdownType.COHORT), ) result = self._get_actors(trends_query=source_query, day="2023-05-01", breakdown=cohort1.pk) @@ -414,7 +414,7 @@ def trends_all_cohort_breakdown_persons(self, inCohortVia: str): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - breakdownFilter=BreakdownFilter(breakdown=[cohort1.pk, "all"], breakdown_type=BreakdownType.cohort), + breakdownFilter=BreakdownFilter(breakdown=[cohort1.pk, "all"], breakdown_type=BreakdownType.COHORT), ) source_query.modifiers = HogQLQueryModifiers(inCohortVia=inCohortVia) @@ -458,7 +458,7 @@ def test_trends_math_weekly_active_persons(self): team=self.team, ) source_query = TrendsQuery( - series=[EventsNode(event="$pageview", math=BaseMathType.weekly_active)], + series=[EventsNode(event="$pageview", math=BaseMathType.WEEKLY_ACTIVE)], dateRange=InsightDateRange(date_from="-7d"), ) @@ -473,7 +473,7 @@ def test_trends_math_weekly_active_persons(self): def test_trends_math_property_sum_persons(self): self._create_events() source_query = TrendsQuery( - series=[EventsNode(event="$pageview", math=PropertyMathType.sum, math_property="some_property")], + series=[EventsNode(event="$pageview", math=PropertyMathType.SUM, math_property="some_property")], dateRange=InsightDateRange(date_from="-7d"), ) @@ -493,7 +493,7 @@ def test_trends_math_count_per_actor_persons(self): source_query = TrendsQuery( series=[ EventsNode( - event="$pageview", math=CountPerActorMathType.max_count_per_actor, math_property="some_property" + event="$pageview", math=CountPerActorMathType.MAX_COUNT_PER_ACTOR, math_property="some_property" ) ], dateRange=InsightDateRange(date_from="-7d"), @@ -537,7 +537,7 @@ def test_trends_math_group_persons(self): ) source_query = TrendsQuery( series=[ - EventsNode(event="$pageview", math="unique_group", math_group_type_index=MathGroupTypeIndex.number_0) + EventsNode(event="$pageview", math="unique_group", math_group_type_index=MathGroupTypeIndex.NUMBER_0) ], dateRange=InsightDateRange(date_from="-7d"), ) @@ -570,7 +570,7 @@ def test_trends_math_group_persons_filters_empty(self): ) source_query = TrendsQuery( series=[ - EventsNode(event="$pageview", math="unique_group", math_group_type_index=MathGroupTypeIndex.number_0) + EventsNode(event="$pageview", math="unique_group", math_group_type_index=MathGroupTypeIndex.NUMBER_0) ], dateRange=InsightDateRange(date_from="-7d"), ) @@ -586,7 +586,7 @@ def test_trends_total_value_persons(self): source_query = TrendsQuery( series=[EventsNode(event="$pageview")], dateRange=InsightDateRange(date_from="-7d"), - trendsFilter=TrendsFilter(display=ChartDisplayType.BoldNumber), + trendsFilter=TrendsFilter(display=ChartDisplayType.BOLD_NUMBER), ) with freeze_time("2023-05-01T20:00:00.000Z"): @@ -607,13 +607,13 @@ def test_trends_compare_persons(self): trendsFilter=TrendsFilter(compare=True), ) - result = self._get_actors(trends_query=source_query, day="2023-05-06", compare=Compare.current) + result = self._get_actors(trends_query=source_query, day="2023-05-06", compare=Compare.CURRENT) self.assertEqual(len(result), 1) self.assertEqual(get_distinct_id(result[0]), "person1") self.assertEqual(get_event_count(result[0]), 1) - result = self._get_actors(trends_query=source_query, day="2023-05-06", compare=Compare.previous) + result = self._get_actors(trends_query=source_query, day="2023-05-06", compare=Compare.PREVIOUS) self.assertEqual(len(result), 2) self.assertEqual(get_distinct_id(result[0]), "person2") diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_query_builder.py b/posthog/hogql_queries/insights/trends/test/test_trends_query_builder.py index 3b9b69fa289c25..4826813cb54dec 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_query_builder.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_query_builder.py @@ -74,7 +74,7 @@ def test_column_names(self): trends_query = TrendsQuery( kind="TrendsQuery", dateRange=InsightDateRange(date_from="2023-01-01"), - series=[EventsNode(event="$pageview", math=BaseMathType.total)], + series=[EventsNode(event="$pageview", math=BaseMathType.TOTAL)], ) response = self.get_response(trends_query) @@ -101,7 +101,7 @@ def assert_column_names_with_display_type_and_breakdowns(self, display_type: Cha dateRange=InsightDateRange(date_from="2023-01-01"), series=[EventsNode(event="$pageview")], trendsFilter=TrendsFilter(display=display_type), - breakdownFilter=BreakdownFilter(breakdown="$geoip_country_code", breakdown_type=BreakdownType.event), + breakdownFilter=BreakdownFilter(breakdown="$geoip_country_code", breakdown_type=BreakdownType.EVENT), ) response = self.get_response(trends_query) @@ -110,20 +110,20 @@ def assert_column_names_with_display_type_and_breakdowns(self, display_type: Cha assert set(response.columns).issubset({"date", "total", "breakdown_value"}) def test_column_names_with_display_type(self): - self.assert_column_names_with_display_type(ChartDisplayType.ActionsAreaGraph) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsBar) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsBarValue) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsLineGraph) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsPie) - self.assert_column_names_with_display_type(ChartDisplayType.BoldNumber) - self.assert_column_names_with_display_type(ChartDisplayType.WorldMap) - self.assert_column_names_with_display_type(ChartDisplayType.ActionsLineGraphCumulative) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_AREA_GRAPH) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_BAR) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_BAR_VALUE) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_LINE_GRAPH) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_PIE) + self.assert_column_names_with_display_type(ChartDisplayType.BOLD_NUMBER) + self.assert_column_names_with_display_type(ChartDisplayType.WORLD_MAP) + self.assert_column_names_with_display_type(ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE) def test_column_names_with_display_type_and_breakdowns(self): - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ActionsAreaGraph) - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ActionsBar) - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ActionsBarValue) - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ActionsLineGraph) - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ActionsPie) - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.WorldMap) - self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ActionsLineGraphCumulative) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ACTIONS_AREA_GRAPH) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ACTIONS_BAR) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ACTIONS_BAR_VALUE) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ACTIONS_LINE_GRAPH) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ACTIONS_PIE) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.WORLD_MAP) + self.assert_column_names_with_display_type_and_breakdowns(ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE) diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py b/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py index d3f92ba85c253a..98a75f49d4a6aa 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py @@ -224,7 +224,7 @@ def test_trends_label(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, None, None, None, @@ -238,7 +238,7 @@ def test_trends_count(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, None, None, None, @@ -252,7 +252,7 @@ def test_trends_data(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, None, None, None, @@ -266,7 +266,7 @@ def test_trends_days(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, None, None, None, @@ -295,7 +295,7 @@ def test_trends_labels(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, None, None, None, @@ -324,7 +324,7 @@ def test_trends_labels_hour(self): response = self._run_trends_query( self.default_date_from, self.default_date_from, - IntervalType.hour, + IntervalType.HOUR, [EventsNode(event="$pageview")], ) @@ -342,7 +342,7 @@ def test_trends_multiple_series(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], ) @@ -363,7 +363,7 @@ def test_formula(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="A+2*B"), ) @@ -379,11 +379,11 @@ def test_formula_total_value(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter( formula="A+2*B", - display=ChartDisplayType.BoldNumber, # total value + display=ChartDisplayType.BOLD_NUMBER, # total value ), ) self.assertEqual(1, len(response.results)) @@ -398,7 +398,7 @@ def test_formula_with_compare(self): response = self._run_trends_query( "2020-01-15", "2020-01-19", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="A+2*B", compare=True), ) @@ -426,11 +426,11 @@ def test_formula_with_compare_total_value(self): response = self._run_trends_query( "2020-01-15", "2020-01-19", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter( formula="A+2*B", - display=ChartDisplayType.BoldNumber, # total value + display=ChartDisplayType.BOLD_NUMBER, # total value compare=True, ), ) @@ -457,10 +457,10 @@ def test_formula_with_breakdown(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="A+2*B"), - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) # one for each breakdown value @@ -485,10 +485,10 @@ def test_formula_with_breakdown_and_compare(self): response = self._run_trends_query( "2020-01-15", "2020-01-19", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="A+2*B", compare=True), - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) # chrome, ff and edge for previous, and chrome and safari for current @@ -515,14 +515,14 @@ def test_formula_with_breakdown_and_compare_total_value(self): response = self._run_trends_query( "2020-01-15", "2020-01-19", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter( formula="A+2*B", - display=ChartDisplayType.BoldNumber, # total value + display=ChartDisplayType.BOLD_NUMBER, # total value compare=True, ), - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) # chrome, ff and edge for previous, and chrome and safari for current @@ -582,10 +582,10 @@ def test_formula_with_multi_cohort_breakdown(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="A+B"), - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort1.pk, cohort2.pk]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort1.pk, cohort2.pk]), ) assert len(response.results) == 2 @@ -624,10 +624,10 @@ def test_formula_with_multi_cohort_all_breakdown(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="A+B"), - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort1.pk, "all"]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort1.pk, "all"]), ) assert len(response.results) == 2 @@ -651,10 +651,10 @@ def test_formula_with_breakdown_and_no_data(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageviewxxx"), EventsNode(event="$pageleavexxx")], TrendsFilter(formula="A+2*B"), - BreakdownFilter(breakdown_type=BreakdownType.person, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.PERSON, breakdown="$browser"), ) self.assertEqual(0, len(response.results)) @@ -662,10 +662,10 @@ def test_formula_with_breakdown_and_no_data(self): response = self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleavexxx")], TrendsFilter(formula="A+2*B"), - BreakdownFilter(breakdown_type=BreakdownType.person, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.PERSON, breakdown="$browser"), ) self.assertEqual([1, 0, 1, 3, 1, 0, 2, 0, 1, 0, 1], response.results[0]["data"]) @@ -676,10 +676,10 @@ def test_breakdown_is_context_aware(self, mock_sync_execute: MagicMock): self._run_trends_query( self.default_date_from, self.default_date_to, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageviewxxx"), EventsNode(event="$pageleavexxx")], TrendsFilter(formula="A+2*B"), - BreakdownFilter(breakdown_type=BreakdownType.person, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.PERSON, breakdown="$browser"), limit_context=LimitContext.QUERY_ASYNC, ) @@ -693,7 +693,7 @@ def test_trends_compare(self): response = self._run_trends_query( "2020-01-15", "2020-01-19", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(compare=True), ) @@ -737,7 +737,7 @@ def test_trends_compare_weeks(self): response = self._run_trends_query( "-7d", None, - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(compare=True), ) @@ -790,10 +790,10 @@ def test_trends_breakdowns(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -815,10 +815,10 @@ def test_trends_breakdowns_boolean(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="bool_field"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="bool_field"), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -838,11 +838,11 @@ def test_trends_breakdowns_histogram(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, BreakdownFilter( - breakdown_type=BreakdownType.event, + breakdown_type=BreakdownType.EVENT, breakdown="prop", breakdown_histogram_bin_count=4, ), @@ -885,10 +885,10 @@ def test_trends_breakdowns_cohort(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort.pk]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort.pk]), ) assert len(response.results) == 1 @@ -916,10 +916,10 @@ def test_trends_breakdowns_hogql(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.hogql, breakdown="properties.$browser"), + BreakdownFilter(breakdown_type=BreakdownType.HOGQL, breakdown="properties.$browser"), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -941,10 +941,10 @@ def test_trends_breakdowns_multiple_hogql(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], None, - BreakdownFilter(breakdown_type=BreakdownType.hogql, breakdown="properties.$browser"), + BreakdownFilter(breakdown_type=BreakdownType.HOGQL, breakdown="properties.$browser"), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -974,10 +974,10 @@ def test_trends_breakdowns_and_compare(self): response = self._run_trends_query( "2020-01-15", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(compare=True), - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -1021,10 +1021,10 @@ def test_trends_breakdown_and_aggregation_query_orchestration(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=PropertyMathType.sum, math_property="prop")], + IntervalType.DAY, + [EventsNode(event="$pageview", math=PropertyMathType.SUM, math_property="prop")], None, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) breakdown_labels = [result["breakdown_value"] for result in response.results] @@ -1100,7 +1100,7 @@ def test_trends_aggregation_hogql(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview", math="hogql", math_hogql="sum(properties.prop)")], None, None, @@ -1128,8 +1128,8 @@ def test_trends_aggregation_total(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=BaseMathType.total)], + IntervalType.DAY, + [EventsNode(event="$pageview", math=BaseMathType.TOTAL)], None, None, ) @@ -1143,8 +1143,8 @@ def test_trends_aggregation_dau(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=BaseMathType.dau)], + IntervalType.DAY, + [EventsNode(event="$pageview", math=BaseMathType.DAU)], None, None, ) @@ -1158,8 +1158,8 @@ def test_trends_aggregation_wau(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=BaseMathType.weekly_active)], + IntervalType.DAY, + [EventsNode(event="$pageview", math=BaseMathType.WEEKLY_ACTIVE)], None, None, ) @@ -1173,8 +1173,8 @@ def test_trends_aggregation_mau(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=BaseMathType.monthly_active)], + IntervalType.DAY, + [EventsNode(event="$pageview", math=BaseMathType.MONTHLY_ACTIVE)], None, None, ) @@ -1188,8 +1188,8 @@ def test_trends_aggregation_unique(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=BaseMathType.unique_session)], + IntervalType.DAY, + [EventsNode(event="$pageview", math=BaseMathType.UNIQUE_SESSION)], None, None, ) @@ -1203,8 +1203,8 @@ def test_trends_aggregation_property_sum(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=PropertyMathType.sum, math_property="prop")], + IntervalType.DAY, + [EventsNode(event="$pageview", math=PropertyMathType.SUM, math_property="prop")], None, None, ) @@ -1231,8 +1231,8 @@ def test_trends_aggregation_property_avg(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=PropertyMathType.avg, math_property="prop")], + IntervalType.DAY, + [EventsNode(event="$pageview", math=PropertyMathType.AVG, math_property="prop")], None, None, ) @@ -1259,8 +1259,8 @@ def test_trends_aggregation_per_actor_max(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, - [EventsNode(event="$pageview", math=CountPerActorMathType.max_count_per_actor)], + IntervalType.DAY, + [EventsNode(event="$pageview", math=CountPerActorMathType.MAX_COUNT_PER_ACTOR)], None, None, ) @@ -1287,9 +1287,9 @@ def test_trends_display_aggregate(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.BoldNumber), + TrendsFilter(display=ChartDisplayType.BOLD_NUMBER), None, ) @@ -1305,9 +1305,9 @@ def test_trends_display_cumulative(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraphCumulative), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE), None, ) @@ -1343,10 +1343,10 @@ def test_breakdown_values_limit(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraph), - BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.event), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), + BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.EVENT), ) self.assertEqual(len(response.results), 26) @@ -1354,20 +1354,20 @@ def test_breakdown_values_limit(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraph), - BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.event, breakdown_limit=10), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), + BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.EVENT, breakdown_limit=10), ) self.assertEqual(len(response.results), 11) response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraph), - BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.event), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), + BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.EVENT), limit_context=LimitContext.EXPORT, ) self.assertEqual(len(response.results), 30) @@ -1386,10 +1386,10 @@ def test_breakdown_values_unknown_property(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraph), - BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.event), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), + BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.EVENT), ) self.assertEqual(len(response.results), 26) @@ -1397,10 +1397,10 @@ def test_breakdown_values_unknown_property(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraph), - BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.event, breakdown_limit=10), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), + BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.EVENT, breakdown_limit=10), ) self.assertEqual(len(response.results), 11) @@ -1419,10 +1419,10 @@ def test_breakdown_values_world_map_limit(self): query_runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.WorldMap), - BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.event), + TrendsFilter(display=ChartDisplayType.WORLD_MAP), + BreakdownFilter(breakdown="breakdown_value", breakdown_type=BreakdownType.EVENT), ) query = query_runner.to_queries()[0] assert isinstance(query, ast.SelectQuery) and query.limit == ast.Constant(value=MAX_SELECT_RETURNED_ROWS) @@ -1436,9 +1436,9 @@ def test_previous_period_with_number_display(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.BoldNumber, compare=True), + TrendsFilter(display=ChartDisplayType.BOLD_NUMBER, compare=True), None, ) @@ -1477,7 +1477,7 @@ def test_formula_rounding(self): response = self._run_trends_query( "2020-01-11T00:00:00Z", "2020-01-11T23:59:59Z", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], TrendsFilter(formula="B/A"), ) @@ -1508,7 +1508,7 @@ def test_properties_filtering_with_materialized_columns_and_empty_string_as_prop response = self._run_trends_query( date_from="2020-01-11T00:00:00Z", date_to="2020-01-11T23:59:59Z", - interval=IntervalType.day, + interval=IntervalType.DAY, series=[EventsNode(event="$pageview")], filter_test_accounts=True, ) @@ -1521,7 +1521,7 @@ def test_smoothing(self): response = self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(smoothingIntervals=7), None, @@ -1574,14 +1574,14 @@ def test_cohort_modifier(self, patch_create_default_modifiers_for_team): self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort1.pk, cohort2.pk]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort1.pk, cohort2.pk]), hogql_modifiers=modifiers, ) - assert modifiers.inCohortVia == InCohortVia.leftjoin_conjoined + assert modifiers.inCohortVia == InCohortVia.LEFTJOIN_CONJOINED @patch("posthog.hogql_queries.query_runner.create_default_modifiers_for_team") def test_cohort_modifier_with_all_cohort(self, patch_create_default_modifiers_for_team): @@ -1628,14 +1628,14 @@ def test_cohort_modifier_with_all_cohort(self, patch_create_default_modifiers_fo self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort1.pk, cohort2.pk, "all"]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort1.pk, cohort2.pk, "all"]), hogql_modifiers=modifiers, ) - assert modifiers.inCohortVia == InCohortVia.auto + assert modifiers.inCohortVia == InCohortVia.AUTO @patch("posthog.hogql_queries.query_runner.create_default_modifiers_for_team") def test_cohort_modifier_with_too_few_cohorts(self, patch_create_default_modifiers_for_team): @@ -1682,14 +1682,14 @@ def test_cohort_modifier_with_too_few_cohorts(self, patch_create_default_modifie self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort1.pk, cohort2.pk, "all"]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort1.pk, cohort2.pk, "all"]), hogql_modifiers=modifiers, ) - assert modifiers.inCohortVia == InCohortVia.auto + assert modifiers.inCohortVia == InCohortVia.AUTO @patch("posthog.hogql_queries.insights.trends.trends_query_runner.execute_hogql_query") def test_should_throw_exception(self, patch_sync_execute): @@ -1699,7 +1699,7 @@ def test_should_throw_exception(self, patch_sync_execute): self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, None, @@ -1717,7 +1717,7 @@ def test_to_actors_query_options(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, None, @@ -1752,7 +1752,7 @@ def test_to_actors_query_options_compare(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], TrendsFilter(compare=True), None, @@ -1790,7 +1790,7 @@ def test_to_actors_query_options_multiple_series(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview"), EventsNode(event="$pageleave")], None, None, @@ -1809,10 +1809,10 @@ def test_to_actors_query_options_breakdowns(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser", breakdown_limit=3), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser", breakdown_limit=3), ) response = runner.to_actors_query_options() @@ -1833,10 +1833,10 @@ def test_to_actors_query_options_breakdowns_boolean(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="bool_field"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="bool_field"), ) response = runner.to_actors_query_options() @@ -1855,11 +1855,11 @@ def test_to_actors_query_options_breakdowns_histogram(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, BreakdownFilter( - breakdown_type=BreakdownType.event, + breakdown_type=BreakdownType.EVENT, breakdown="prop", breakdown_histogram_bin_count=4, ), @@ -1901,10 +1901,10 @@ def test_to_actors_query_options_breakdowns_cohort(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort.pk]), + BreakdownFilter(breakdown_type=BreakdownType.COHORT, breakdown=[cohort.pk]), ) response = runner.to_actors_query_options() @@ -1920,10 +1920,10 @@ def test_to_actors_query_options_breakdowns_hogql(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, - BreakdownFilter(breakdown_type=BreakdownType.hogql, breakdown="properties.$browser"), + BreakdownFilter(breakdown_type=BreakdownType.HOGQL, breakdown="properties.$browser"), ) response = runner.to_actors_query_options() @@ -1944,10 +1944,10 @@ def test_to_actors_query_options_bar_value(self): runner = self._create_query_runner( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsBarValue), - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + TrendsFilter(display=ChartDisplayType.ACTIONS_BAR_VALUE), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) response = runner.to_actors_query_options() @@ -1966,7 +1966,7 @@ def test_limit_is_context_aware(self, mock_sync_execute: MagicMock): self._run_trends_query( "2020-01-09", "2020-01-20", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], limit_context=LimitContext.QUERY_ASYNC, ) @@ -1981,7 +1981,7 @@ def test_actors_query_explicit_dates(self): runner = self._create_query_runner( "2020-01-09 12:37:42", "2020-01-20 12:37:42", - IntervalType.day, + IntervalType.DAY, [EventsNode(event="$pageview")], None, None, @@ -2024,9 +2024,9 @@ def test_sampling_adjustment(self): runner = self._create_query_runner( "2020-01-01", "2020-01-31", - IntervalType.month, + IntervalType.MONTH, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.ActionsLineGraph), + TrendsFilter(display=ChartDisplayType.ACTIONS_LINE_GRAPH), ) runner.query.samplingFactor = 0.1 response = runner.calculate() @@ -2038,9 +2038,9 @@ def test_sampling_adjustment(self): runner = self._create_query_runner( "2020-01-01", "2020-01-31", - IntervalType.month, + IntervalType.MONTH, [EventsNode(event="$pageview")], - TrendsFilter(display=ChartDisplayType.BoldNumber), + TrendsFilter(display=ChartDisplayType.BOLD_NUMBER), ) runner.query.samplingFactor = 0.1 response = runner.calculate() diff --git a/posthog/hogql_queries/insights/trends/test/test_utils.py b/posthog/hogql_queries/insights/trends/test/test_utils.py index 5fecab14914b77..e4527fae76e315 100644 --- a/posthog/hogql_queries/insights/trends/test/test_utils.py +++ b/posthog/hogql_queries/insights/trends/test/test_utils.py @@ -4,58 +4,60 @@ def test_properties_chain_person(): - p1 = get_properties_chain(breakdown_type=BreakdownType.person, breakdown_field="field", group_type_index=None) + p1 = get_properties_chain(breakdown_type=BreakdownType.PERSON, breakdown_field="field", group_type_index=None) assert p1 == ["person", "properties", "field"] - p2 = get_properties_chain(breakdown_type=BreakdownType.person, breakdown_field="field", group_type_index=1) + p2 = get_properties_chain(breakdown_type=BreakdownType.PERSON, breakdown_field="field", group_type_index=1) assert p2 == ["person", "properties", "field"] def test_properties_chain_session(): - p1 = get_properties_chain(breakdown_type=BreakdownType.session, breakdown_field="anything", group_type_index=None) + p1 = get_properties_chain(breakdown_type=BreakdownType.SESSION, breakdown_field="anything", group_type_index=None) assert p1 == ["session", "anything"] - p2 = get_properties_chain(breakdown_type=BreakdownType.session, breakdown_field="anything", group_type_index=1) + p2 = get_properties_chain(breakdown_type=BreakdownType.SESSION, breakdown_field="anything", group_type_index=1) assert p2 == ["session", "anything"] p3 = get_properties_chain( - breakdown_type=BreakdownType.session, breakdown_field="$session_duration", group_type_index=None + breakdown_type=BreakdownType.SESSION, breakdown_field="$session_duration", group_type_index=None ) assert p3 == ["session", "$session_duration"] def test_properties_chain_groups(): - p1 = get_properties_chain(breakdown_type=BreakdownType.group, breakdown_field="anything", group_type_index=1) + p1 = get_properties_chain(breakdown_type=BreakdownType.GROUP, breakdown_field="anything", group_type_index=1) assert p1 == ["group_1", "properties", "anything"] with pytest.raises(Exception) as e: - get_properties_chain(breakdown_type=BreakdownType.group, breakdown_field="anything", group_type_index=None) + get_properties_chain(breakdown_type=BreakdownType.GROUP, breakdown_field="anything", group_type_index=None) assert "group_type_index missing from params" in str(e.value) def test_properties_chain_events(): - p1 = get_properties_chain(breakdown_type=BreakdownType.event, breakdown_field="anything", group_type_index=None) + p1 = get_properties_chain(breakdown_type=BreakdownType.EVENT, breakdown_field="anything", group_type_index=None) assert p1 == ["properties", "anything"] - p2 = get_properties_chain(breakdown_type=BreakdownType.event, breakdown_field="anything_else", group_type_index=1) + p2 = get_properties_chain(breakdown_type=BreakdownType.EVENT, breakdown_field="anything_else", group_type_index=1) assert p2 == ["properties", "anything_else"] def test_properties_chain_warehouse_props(): p1 = get_properties_chain( - breakdown_type=BreakdownType.data_warehouse_person_property, + breakdown_type=BreakdownType.DATA_WAREHOUSE_PERSON_PROPERTY, breakdown_field="some_table.field", group_type_index=None, ) assert p1 == ["person", "some_table", "field"] p2 = get_properties_chain( - breakdown_type=BreakdownType.data_warehouse_person_property, breakdown_field="some_table", group_type_index=None + breakdown_type=BreakdownType.DATA_WAREHOUSE_PERSON_PROPERTY, + breakdown_field="some_table", + group_type_index=None, ) assert p2 == ["person", "some_table"] p3 = get_properties_chain( - breakdown_type=BreakdownType.data_warehouse_person_property, + breakdown_type=BreakdownType.DATA_WAREHOUSE_PERSON_PROPERTY, breakdown_field="some_table.props.obj.blah", group_type_index=None, ) diff --git a/posthog/hogql_queries/insights/trends/trends_actors_query_builder.py b/posthog/hogql_queries/insights/trends/trends_actors_query_builder.py index 1eff2f0aae9ccb..c29be46cc791c6 100644 --- a/posthog/hogql_queries/insights/trends/trends_actors_query_builder.py +++ b/posthog/hogql_queries/insights/trends/trends_actors_query_builder.py @@ -119,7 +119,7 @@ def trends_aggregation_operations(self) -> AggregationOperations: def is_compare_previous(self) -> bool: return ( bool(self.trends_query.trendsFilter and self.trends_query.trendsFilter.compare) - and self.compare_value == Compare.previous + and self.compare_value == Compare.PREVIOUS ) @cached_property @@ -128,11 +128,11 @@ def is_active_users_math(self) -> bool: @cached_property def is_weekly_active_math(self) -> bool: - return self.entity.math == BaseMathType.weekly_active + return self.entity.math == BaseMathType.WEEKLY_ACTIVE @cached_property def is_monthly_active_math(self) -> bool: - return self.entity.math == BaseMathType.monthly_active + return self.entity.math == BaseMathType.MONTHLY_ACTIVE @cached_property def is_hourly(self) -> bool: diff --git a/posthog/hogql_queries/insights/trends/trends_query_builder.py b/posthog/hogql_queries/insights/trends/trends_query_builder.py index 00b7fad0573845..015e269e5628e4 100644 --- a/posthog/hogql_queries/insights/trends/trends_query_builder.py +++ b/posthog/hogql_queries/insights/trends/trends_query_builder.py @@ -136,7 +136,7 @@ def _get_events_subquery( # For cumulative unique users or groups, we want to count each user or group once per query, not per day if ( self.query.trendsFilter - and self.query.trendsFilter.display == ChartDisplayType.ActionsLineGraphCumulative + and self.query.trendsFilter.display == ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE and (self.series.math == "unique_group" or self.series.math == "dau") ): day_start.expr = ast.Call(name="min", args=[day_start.expr]) @@ -237,7 +237,8 @@ def _get_events_subquery( return default_query def _outer_select_query(self, breakdown: Breakdown, inner_query: ast.SelectQuery) -> ast.SelectQuery: - total_array = parse_expr(""" + total_array = parse_expr( + """ arrayMap( _match_date -> arraySum( @@ -249,9 +250,10 @@ def _outer_select_query(self, breakdown: Breakdown, inner_query: ast.SelectQuery ), date ) - """) + """ + ) - if self._trends_display.display_type == ChartDisplayType.ActionsLineGraphCumulative: + if self._trends_display.display_type == ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE: # fill zeros in with the previous value total_array = parse_expr( """ diff --git a/posthog/hogql_queries/insights/trends/trends_query_runner.py b/posthog/hogql_queries/insights/trends/trends_query_runner.py index cbd5f87e1c4c50..d64e80a38330ad 100644 --- a/posthog/hogql_queries/insights/trends/trends_query_runner.py +++ b/posthog/hogql_queries/insights/trends/trends_query_runner.py @@ -157,7 +157,7 @@ def to_actors_query( include_recordings: Optional[bool] = None, ) -> ast.SelectQuery | ast.SelectUnionQuery: with self.timings.measure("trends_to_actors_query"): - if self.query.breakdownFilter and self.query.breakdownFilter.breakdown_type == BreakdownType.cohort: + if self.query.breakdownFilter and self.query.breakdownFilter.breakdown_type == BreakdownType.COHORT: if self.query.breakdownFilter.breakdown in ("all", ["all"]) or breakdown_value == "all": self.query.breakdownFilter = None elif isinstance(self.query.breakdownFilter.breakdown, list): @@ -443,7 +443,7 @@ def get_value(name: str, val: Any): }, } else: - if self._trends_display.display_type == ChartDisplayType.ActionsLineGraphCumulative: + if self._trends_display.display_type == ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE: count = get_value("total", val)[-1] else: count = float(sum(get_value("total", val))) @@ -587,14 +587,14 @@ def series_event(self, series: Union[EventsNode, ActionsNode, DataWarehouseNode] def update_hogql_modifiers(self) -> None: if ( - self.modifiers.inCohortVia == InCohortVia.auto + self.modifiers.inCohortVia == InCohortVia.AUTO and self.query.breakdownFilter is not None and self.query.breakdownFilter.breakdown_type == "cohort" and isinstance(self.query.breakdownFilter.breakdown, list) and len(self.query.breakdownFilter.breakdown) > 1 and not any(value == "all" for value in self.query.breakdownFilter.breakdown) ): - self.modifiers.inCohortVia = InCohortVia.leftjoin_conjoined + self.modifiers.inCohortVia = InCohortVia.LEFTJOIN_CONJOINED datawarehouse_modifiers = [] for series in self.query.series: @@ -623,7 +623,7 @@ def setup_series(self) -> list[SeriesWithExtras]: ] if ( - self.modifiers.inCohortVia != InCohortVia.leftjoin_conjoined + self.modifiers.inCohortVia != InCohortVia.LEFTJOIN_CONJOINED and self.query.breakdownFilter is not None and self.query.breakdownFilter.breakdown_type == "cohort" ): @@ -696,7 +696,7 @@ def apply_formula( and self.query.breakdownFilter.breakdown_type == "cohort" and isinstance(self.query.breakdownFilter.breakdown, list) and "all" in self.query.breakdownFilter.breakdown - and self.modifiers.inCohortVia != InCohortVia.leftjoin_conjoined + and self.modifiers.inCohortVia != InCohortVia.LEFTJOIN_CONJOINED and not in_breakdown_clause and self.query.trendsFilter and self.query.trendsFilter.formula @@ -888,7 +888,7 @@ def _query_to_filter(self) -> dict[str, Any]: @cached_property def _trends_display(self) -> TrendsDisplay: if self.query.trendsFilter is None or self.query.trendsFilter.display is None: - display = ChartDisplayType.ActionsLineGraph + display = ChartDisplayType.ACTIONS_LINE_GRAPH else: display = self.query.trendsFilter.display diff --git a/posthog/hogql_queries/insights/utils/test/test_entities.py b/posthog/hogql_queries/insights/utils/test/test_entities.py index df89b129031b48..587f9e8c9cc3fd 100644 --- a/posthog/hogql_queries/insights/utils/test/test_entities.py +++ b/posthog/hogql_queries/insights/utils/test/test_entities.py @@ -22,10 +22,10 @@ (ActionsNode(id=1), ActionsNode(id=2), False), ( EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), True, ), @@ -44,50 +44,50 @@ # different type ( EventsNode( - properties=[PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), False, ), # different key ( EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="other_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="other_key", value="some_value", operator=PropertyOperator.EXACT)] ), False, ), # different value ( EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="other_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="other_value", operator=PropertyOperator.EXACT)] ), False, ), # different operator ( EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.is_not)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.IS_NOT)] ), False, ), # different fixed properties ( EventsNode( - fixedProperties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + fixedProperties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - fixedProperties=[EventPropertyFilter(key="other_key", value="some_value", operator=PropertyOperator.exact)] + fixedProperties=[EventPropertyFilter(key="other_key", value="some_value", operator=PropertyOperator.EXACT)] ), False, ), @@ -103,10 +103,10 @@ def test_is_equal(a, b, expected): # everything equal ( EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), True, ), @@ -114,24 +114,24 @@ def test_is_equal(a, b, expected): ( EventsNode( properties=[ - EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact), - PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact), + EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT), + PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT), ] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), True, ), # subset ( EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( properties=[ - EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact), - PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact), + EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT), + PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT), ] ), False, @@ -154,10 +154,10 @@ def test_is_equal(a, b, expected): # different type ( EventsNode( - properties=[PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[PersonPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), EventsNode( - properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.exact)] + properties=[EventPropertyFilter(key="some_key", value="some_value", operator=PropertyOperator.EXACT)] ), False, ), diff --git a/posthog/hogql_queries/legacy_compatibility/filter_to_query.py b/posthog/hogql_queries/legacy_compatibility/filter_to_query.py index 5510b198257dff..cad685ec9675ee 100644 --- a/posthog/hogql_queries/legacy_compatibility/filter_to_query.py +++ b/posthog/hogql_queries/legacy_compatibility/filter_to_query.py @@ -40,16 +40,16 @@ class MathAvailability(str, Enum): actors_only_math_types = [ - BaseMathType.dau, - BaseMathType.weekly_active, - BaseMathType.monthly_active, + BaseMathType.DAU, + BaseMathType.WEEKLY_ACTIVE, + BaseMathType.MONTHLY_ACTIVE, "unique_group", "hogql", ] def clean_display(display: str): - if display not in ChartDisplayType.__members__: + if display not in [c.value for c in ChartDisplayType]: return None else: return display @@ -81,7 +81,7 @@ def legacy_entity_to_node( and math_availability == MathAvailability.ActorsOnly and entity.math not in actors_only_math_types ): - shared = {**shared, "math": BaseMathType.dau} + shared = {**shared, "math": BaseMathType.DAU} else: shared = { **shared, @@ -321,7 +321,7 @@ def _insight_filter(filter: dict): # Backwards compatibility # Before Filter.funnel_viz_type funnel trends were indicated by Filter.display being TRENDS_LINEAR if funnel_viz_type is None and filter.get("display") == "ActionsLineGraph": - funnel_viz_type = FunnelVizType.trends + funnel_viz_type = FunnelVizType.TRENDS insight_filter = { "funnelsFilter": FunnelsFilter( diff --git a/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py b/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py index 13f5ea9f4d7f62..87c007c1108337 100644 --- a/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py +++ b/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py @@ -1007,9 +1007,9 @@ def test_series_custom(self): query.series, [ ActionsNode(id=1), - ActionsNode(id=1, math=BaseMathType.dau), + ActionsNode(id=1, math=BaseMathType.DAU), EventsNode(event="$pageview", name="$pageview"), - EventsNode(event="$pageview", name="$pageview", math=BaseMathType.dau), + EventsNode(event="$pageview", name="$pageview", math=BaseMathType.DAU), ], ) @@ -1037,7 +1037,7 @@ def test_series_data_warehouse(self): DataWarehouseNode( id="some_table", name="some_table", - math=BaseMathType.total, + math=BaseMathType.TOTAL, table_name="some_table", id_field="id", timestamp_field="created_at", @@ -1061,9 +1061,9 @@ def test_series_order(self): self.assertEqual( query.series, [ - ActionsNode(id=1, math=BaseMathType.dau), + ActionsNode(id=1, math=BaseMathType.DAU), EventsNode(event="$pageview", name="$pageview"), - EventsNode(event="$pageview", name="$pageview", math=BaseMathType.dau), + EventsNode(event="$pageview", name="$pageview", math=BaseMathType.DAU), ActionsNode(id=1), ], ) @@ -1100,23 +1100,23 @@ def test_series_math(self): self.assertEqual( query.series, [ - EventsNode(event="$pageview", name="$pageview", math=BaseMathType.dau), + EventsNode(event="$pageview", name="$pageview", math=BaseMathType.DAU), EventsNode( event="$pageview", name="$pageview", - math=PropertyMathType.median, + math=PropertyMathType.MEDIAN, math_property="$math_prop", ), EventsNode( event="$pageview", name="$pageview", - math=CountPerActorMathType.avg_count_per_actor, + math=CountPerActorMathType.AVG_COUNT_PER_ACTOR, ), EventsNode( event="$pageview", name="$pageview", math="unique_group", - math_group_type_index=MathGroupTypeIndex.number_0, + math_group_type_index=MathGroupTypeIndex.NUMBER_0, ), EventsNode( event="$pageview", @@ -1235,7 +1235,7 @@ def test_series_properties(self): EventPropertyFilter( key="success", value=["true"], - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ) ], ), @@ -1246,7 +1246,7 @@ def test_series_properties(self): PersonPropertyFilter( key="email", value="is_set", - operator=PropertyOperator.is_set, + operator=PropertyOperator.IS_SET, ) ], ), @@ -1255,16 +1255,16 @@ def test_series_properties(self): name="$pageview", properties=[ ElementPropertyFilter( - key=Key.text, + key=Key.TEXT, value=["some text"], - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ) ], ), EventsNode( event="$pageview", name="$pageview", - properties=[SessionPropertyFilter(key="$session_duration", value=1, operator=PropertyOperator.gt)], + properties=[SessionPropertyFilter(key="$session_duration", value=1, operator=PropertyOperator.GT)], ), EventsNode( event="$pageview", @@ -1278,7 +1278,7 @@ def test_series_properties(self): GroupPropertyFilter( key="name", value=["Hedgebox Inc."], - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, group_type_index=2, ) ], @@ -1295,12 +1295,12 @@ def test_series_properties(self): EventPropertyFilter( key="$referring_domain", value="google", - operator=PropertyOperator.icontains, + operator=PropertyOperator.ICONTAINS, ), EventPropertyFilter( key="utm_source", value="is_not_set", - operator=PropertyOperator.is_not_set, + operator=PropertyOperator.IS_NOT_SET, ), ], ), @@ -1315,7 +1315,7 @@ def test_breakdown(self): assert isinstance(query, TrendsQuery) self.assertEqual( query.breakdownFilter, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) def test_breakdown_converts_multi(self): @@ -1326,7 +1326,7 @@ def test_breakdown_converts_multi(self): assert isinstance(query, TrendsQuery) self.assertEqual( query.breakdownFilter, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="$browser"), ) def test_breakdown_type_default(self): @@ -1337,7 +1337,7 @@ def test_breakdown_type_default(self): assert isinstance(query, TrendsQuery) self.assertEqual( query.breakdownFilter, - BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="some_prop"), + BreakdownFilter(breakdown_type=BreakdownType.EVENT, breakdown="some_prop"), ) def test_trends_filter(self): @@ -1363,11 +1363,11 @@ def test_trends_filter(self): TrendsFilter( smoothingIntervals=2, compare=True, - aggregationAxisFormat=AggregationAxisFormat.duration_ms, + aggregationAxisFormat=AggregationAxisFormat.DURATION_MS, aggregationAxisPrefix="pre", aggregationAxisPostfix="post", formula="A + B", - display=ChartDisplayType.ActionsAreaGraph, + display=ChartDisplayType.ACTIONS_AREA_GRAPH, decimalPlaces=5, showLegend=True, showPercentStackView=True, @@ -1427,14 +1427,14 @@ def test_funnels_filter(self): self.assertEqual( query.funnelsFilter, FunnelsFilter( - funnelVizType=FunnelVizType.steps, + funnelVizType=FunnelVizType.STEPS, funnelFromStep=1, funnelToStep=2, - funnelWindowIntervalUnit=FunnelConversionWindowTimeUnit.hour, + funnelWindowIntervalUnit=FunnelConversionWindowTimeUnit.HOUR, funnelWindowInterval=13, - breakdownAttributionType=BreakdownAttributionType.step, + breakdownAttributionType=BreakdownAttributionType.STEP, breakdownAttributionValue=2, - funnelOrderType=StepOrderValue.strict, + funnelOrderType=StepOrderValue.STRICT, exclusions=[ FunnelExclusionEventsNode( event="$pageview", @@ -1477,9 +1477,9 @@ def test_retention_filter(self): self.assertEqual( query.retentionFilter, RetentionFilter( - retentionType=RetentionType.retention_first_time, + retentionType=RetentionType.RETENTION_FIRST_TIME, totalIntervals=12, - period=RetentionPeriod.Week, + period=RetentionPeriod.WEEK, returningEntity={ "id": "$pageview", "name": "$pageview", @@ -1539,7 +1539,7 @@ def test_paths_filter(self): self.assertEqual( query.pathsFilter, PathsFilter( - includeEventTypes=[PathType.field_pageview, PathType.hogql], + includeEventTypes=[PathType.FIELD_PAGEVIEW, PathType.HOGQL], pathsHogQLExpression="event", startPoint="http://localhost:8000/events", endPoint="http://localhost:8000/home", @@ -1558,14 +1558,14 @@ def test_paths_filter(self): self.assertEqual( query.funnelPathsFilter, FunnelPathsFilter( - funnelPathType=FunnelPathType.funnel_path_between_steps, + funnelPathType=FunnelPathType.FUNNEL_PATH_BETWEEN_STEPS, funnelSource=FunnelsQuery( series=[ EventsNode(event="$pageview", name="$pageview"), EventsNode(event=None, name="All events"), ], filterTestAccounts=True, - funnelsFilter=FunnelsFilter(funnelVizType=FunnelVizType.steps, exclusions=[]), + funnelsFilter=FunnelsFilter(funnelVizType=FunnelVizType.STEPS, exclusions=[]), breakdownFilter=BreakdownFilter(), dateRange=InsightDateRange(), ), @@ -1605,6 +1605,6 @@ def test_lifecycle_filter(self): query.lifecycleFilter, LifecycleFilter( showValuesOnSeries=True, - toggledLifecycles=[LifecycleToggle.new, LifecycleToggle.dormant], + toggledLifecycles=[LifecycleToggle.NEW, LifecycleToggle.DORMANT], ), ) diff --git a/posthog/hogql_queries/query_runner.py b/posthog/hogql_queries/query_runner.py index 850ca5b663e3de..8fe0c0f3367861 100644 --- a/posthog/hogql_queries/query_runner.py +++ b/posthog/hogql_queries/query_runner.py @@ -543,13 +543,15 @@ def apply_dashboard_filters(self, dashboard_filter: DashboardFilter): if self.query.properties: try: self.query.properties = PropertyGroupFilter( - type=FilterLogicalOperator.AND, + type=FilterLogicalOperator.AND_, values=[ - PropertyGroupFilterValue(type=FilterLogicalOperator.AND, values=self.query.properties) - if isinstance(self.query.properties, list) - else PropertyGroupFilterValue(**self.query.properties.model_dump()), + ( + PropertyGroupFilterValue(type=FilterLogicalOperator.AND_, values=self.query.properties) + if isinstance(self.query.properties, list) + else PropertyGroupFilterValue(**self.query.properties.model_dump()) + ), PropertyGroupFilterValue( - type=FilterLogicalOperator.AND, values=dashboard_filter.properties + type=FilterLogicalOperator.AND_, values=dashboard_filter.properties ), ], ) diff --git a/posthog/hogql_queries/test/test_actors_query_runner.py b/posthog/hogql_queries/test/test_actors_query_runner.py index d6bed9fec969b3..904c1adad8d9fa 100644 --- a/posthog/hogql_queries/test/test_actors_query_runner.py +++ b/posthog/hogql_queries/test/test_actors_query_runner.py @@ -94,7 +94,7 @@ def test_persons_query_properties(self): PersonPropertyFilter( key="random_uuid", value=self.random_uuid, - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ), HogQLPropertyFilter(key="toInt(properties.index) > 5"), ] @@ -110,7 +110,7 @@ def test_persons_query_fixed_properties(self): PersonPropertyFilter( key="random_uuid", value=self.random_uuid, - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ), HogQLPropertyFilter(key="toInt(properties.index) < 2"), ] @@ -221,10 +221,10 @@ def test_source_lifecycle_query(self): PersonPropertyFilter( key="random_uuid", value=self.random_uuid, - operator=PropertyOperator.exact, + operator=PropertyOperator.EXACT, ) ], - interval=IntervalType.day, + interval=IntervalType.DAY, dateRange=InsightDateRange(date_from="-7d"), ) query = ActorsQuery( diff --git a/posthog/hogql_queries/test/test_events_query_runner.py b/posthog/hogql_queries/test/test_events_query_runner.py index 345df985bf6fa4..f42fe3dc65755d 100644 --- a/posthog/hogql_queries/test/test_events_query_runner.py +++ b/posthog/hogql_queries/test/test_events_query_runner.py @@ -97,8 +97,8 @@ def test_is_not_set_boolean(self): EventPropertyFilter( type="event", key="boolean_field", - operator=PropertyOperator.is_not_set, - value=PropertyOperator.is_not_set, + operator=PropertyOperator.IS_NOT_SET, + value=PropertyOperator.IS_NOT_SET, ) ) @@ -111,8 +111,8 @@ def test_is_set_boolean(self): EventPropertyFilter( type="event", key="boolean_field", - operator=PropertyOperator.is_set, - value=PropertyOperator.is_set, + operator=PropertyOperator.IS_SET, + value=PropertyOperator.IS_SET, ) ) diff --git a/posthog/hogql_queries/test/test_query_runner.py b/posthog/hogql_queries/test/test_query_runner.py index 6b43b0c76e2da0..96003badffd033 100644 --- a/posthog/hogql_queries/test/test_query_runner.py +++ b/posthog/hogql_queries/test/test_query_runner.py @@ -200,7 +200,7 @@ def test_modifier_passthrough(self): runner = HogQLQueryRunner( query=HogQLQuery(query="select properties.$browser from events"), team=self.team, - modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.legacy_null_as_string), + modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.LEGACY_NULL_AS_STRING), ) response = runner.calculate() assert response.clickhouse is not None @@ -209,7 +209,7 @@ def test_modifier_passthrough(self): runner = HogQLQueryRunner( query=HogQLQuery(query="select properties.$browser from events"), team=self.team, - modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.disabled), + modifiers=HogQLQueryModifiers(materializationMode=MaterializationMode.DISABLED), ) response = runner.calculate() assert response.clickhouse is not None diff --git a/posthog/hogql_queries/utils/query_date_range.py b/posthog/hogql_queries/utils/query_date_range.py index ecdf9411b00540..1f5d5bf7996a1b 100644 --- a/posthog/hogql_queries/utils/query_date_range.py +++ b/posthog/hogql_queries/utils/query_date_range.py @@ -38,10 +38,10 @@ def __init__( ) -> None: self._team = team self._date_range = date_range - self._interval = interval or IntervalType.day + self._interval = interval or IntervalType.DAY self._now_without_timezone = now - if not isinstance(self._interval, IntervalType) or re.match(r"[^a-z]", self._interval.name): + if not isinstance(self._interval, IntervalType) or re.match(r"[^a-z]", "DAY", re.IGNORECASE): raise ValueError(f"Invalid interval: {interval}") def date_to(self) -> datetime: @@ -114,18 +114,18 @@ def previous_period_date_from_str(self) -> str: @cached_property def interval_type(self) -> IntervalType: - return self._interval or IntervalType.day + return self._interval or IntervalType.DAY @cached_property def interval_name(self) -> IntervalLiteral: - return cast(IntervalLiteral, self.interval_type.name) + return cast(IntervalLiteral, self.interval_type.name.lower()) @cached_property def is_hourly(self) -> bool: if self._interval is None: return False - return self._interval == IntervalType.hour + return self._interval == IntervalType.HOUR @cached_property def explicit(self) -> bool: @@ -229,9 +229,9 @@ def use_start_of_interval(self): is_delta_hours = delta_mapping.get("hours", None) is not None - if interval in (IntervalType.hour, IntervalType.minute): + if interval in (IntervalType.HOUR, IntervalType.MINUTE): return False - elif interval == IntervalType.day: + elif interval == IntervalType.DAY: if is_delta_hours: return False return True @@ -310,15 +310,15 @@ def determine_time_delta(total_intervals: int, period: str) -> timedelta: def date_from(self) -> datetime: delta = self.determine_time_delta(self.total_intervals, self._interval.name) - if self._interval in (IntervalType.hour, IntervalType.minute): + if self._interval in (IntervalType.HOUR, IntervalType.MINUTE): return self.date_to() - delta - elif self._interval == IntervalType.week: + elif self._interval == IntervalType.WEEK: date_from = self.date_to() - delta week_start_alignment_days = date_from.isoweekday() % 7 if self._team.week_start_day == WeekStartDay.MONDAY: week_start_alignment_days = date_from.weekday() return date_from - timedelta(days=week_start_alignment_days) - elif self._interval == IntervalType.month: + elif self._interval == IntervalType.MONTH: return self.date_to().replace(day=1, hour=0, minute=0, second=0, microsecond=0) - delta else: date_to = self.date_to().replace(hour=0, minute=0, second=0, microsecond=0) diff --git a/posthog/hogql_queries/utils/test/test_query_date_range.py b/posthog/hogql_queries/utils/test/test_query_date_range.py index c645df77448022..c87c29ebcbd723 100644 --- a/posthog/hogql_queries/utils/test/test_query_date_range.py +++ b/posthog/hogql_queries/utils/test/test_query_date_range.py @@ -13,14 +13,14 @@ class TestQueryDateRange(APIBaseTest): def test_parsed_date(self): now = parser.isoparse("2021-08-25T00:00:00.000Z") date_range = InsightDateRange(date_from="-48h") - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.day, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.DAY, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-08-23T00:00:00Z")) self.assertEqual(query_date_range.date_to(), parser.isoparse("2021-08-25T23:59:59.999999Z")) def test_parsed_date_hour(self): now = parser.isoparse("2021-08-25T00:00:00.000Z") date_range = InsightDateRange(date_from="-48h") - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.hour, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.HOUR, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-08-23T00:00:00Z")) self.assertEqual( @@ -30,7 +30,7 @@ def test_parsed_date_hour(self): def test_parsed_date_middle_of_hour(self): now = parser.isoparse("2021-08-25T00:00:00.000Z") date_range = InsightDateRange(date_from="2021-08-23 05:00:00", date_to="2021-08-26 07:00:00") - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.hour, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.HOUR, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-08-23 05:00:00Z")) self.assertEqual( @@ -40,7 +40,7 @@ def test_parsed_date_middle_of_hour(self): def test_parsed_date_week(self): now = parser.isoparse("2021-08-25T00:00:00.000Z") date_range = InsightDateRange(date_from="-7d") - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.week, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.WEEK, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-08-18 00:00:00Z")) self.assertEqual(query_date_range.date_to(), parser.isoparse("2021-08-25 23:59:59.999999Z")) @@ -49,13 +49,13 @@ def test_all_values(self): now = parser.isoparse("2021-08-25T00:00:00.000Z") self.assertEqual( QueryDateRange( - team=self.team, date_range=InsightDateRange(date_from="-20h"), interval=IntervalType.day, now=now + team=self.team, date_range=InsightDateRange(date_from="-20h"), interval=IntervalType.DAY, now=now ).all_values(), [parser.isoparse("2021-08-24T00:00:00Z"), parser.isoparse("2021-08-25T00:00:00Z")], ) self.assertEqual( QueryDateRange( - team=self.team, date_range=InsightDateRange(date_from="-20d"), interval=IntervalType.week, now=now + team=self.team, date_range=InsightDateRange(date_from="-20d"), interval=IntervalType.WEEK, now=now ).all_values(), [ parser.isoparse("2021-08-01T00:00:00Z"), @@ -67,7 +67,7 @@ def test_all_values(self): self.team.week_start_day = WeekStartDay.MONDAY self.assertEqual( QueryDateRange( - team=self.team, date_range=InsightDateRange(date_from="-20d"), interval=IntervalType.week, now=now + team=self.team, date_range=InsightDateRange(date_from="-20d"), interval=IntervalType.WEEK, now=now ).all_values(), [ parser.isoparse("2021-08-02T00:00:00Z"), @@ -78,13 +78,13 @@ def test_all_values(self): ) self.assertEqual( QueryDateRange( - team=self.team, date_range=InsightDateRange(date_from="-50d"), interval=IntervalType.month, now=now + team=self.team, date_range=InsightDateRange(date_from="-50d"), interval=IntervalType.MONTH, now=now ).all_values(), [parser.isoparse("2021-07-01T00:00:00Z"), parser.isoparse("2021-08-01T00:00:00Z")], ) self.assertEqual( QueryDateRange( - team=self.team, date_range=InsightDateRange(date_from="-3h"), interval=IntervalType.hour, now=now + team=self.team, date_range=InsightDateRange(date_from="-3h"), interval=IntervalType.HOUR, now=now ).all_values(), [ parser.isoparse("2021-08-24T21:00:00Z"), @@ -99,7 +99,7 @@ def test_date_to_explicit(self): date_range = InsightDateRange( date_from="2021-02-25T12:25:23.000Z", date_to="2021-04-25T10:59:23.000Z", explicitDate=True ) - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.day, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.DAY, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-02-25T12:25:23.000Z")) self.assertEqual(query_date_range.date_to(), parser.isoparse("2021-04-25T10:59:23.000Z")) @@ -108,12 +108,12 @@ def test_yesterday(self): now = parser.isoparse("2021-08-25T00:00:00.000Z") date_range = InsightDateRange(date_from="-1dStart", date_to="-1dEnd", explicitDate=False) - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.hour, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.HOUR, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-08-24T00:00:00.000000Z")) self.assertEqual(query_date_range.date_to(), parser.isoparse("2021-08-24T23:59:59.999999Z")) - query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.day, now=now) + query_date_range = QueryDateRange(team=self.team, date_range=date_range, interval=IntervalType.DAY, now=now) self.assertEqual(query_date_range.date_from(), parser.isoparse("2021-08-24T00:00:00.000000Z")) self.assertEqual(query_date_range.date_to(), parser.isoparse("2021-08-24T23:59:59.999999Z")) @@ -125,7 +125,7 @@ def setUp(self): self.total_intervals = 5 def test_constructor_initialization(self): - query = QueryDateRangeWithIntervals(None, self.total_intervals, self.team, IntervalType.day, self.now) + query = QueryDateRangeWithIntervals(None, self.total_intervals, self.team, IntervalType.DAY, self.now) self.assertEqual(query.total_intervals, self.total_intervals) def test_determine_time_delta_valid(self): @@ -137,44 +137,44 @@ def test_determine_time_delta_invalid_period(self): QueryDateRangeWithIntervals.determine_time_delta(5, "decade") def test_date_from_day_interval(self): - query = QueryDateRangeWithIntervals(None, 2, self.team, IntervalType.day, self.now) + query = QueryDateRangeWithIntervals(None, 2, self.team, IntervalType.DAY, self.now) self.assertEqual(query.date_from(), parser.isoparse("2021-08-24T00:00:00Z")) def test_date_from_hour_interval(self): - query = QueryDateRangeWithIntervals(None, 48, self.team, IntervalType.hour, self.now) + query = QueryDateRangeWithIntervals(None, 48, self.team, IntervalType.HOUR, self.now) self.assertEqual(query.date_from(), parser.isoparse("2021-08-23T01:00:00Z")) def test_date_from_week_interval_starting_monday(self): self.team.week_start_day = WeekStartDay.MONDAY - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.week, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.WEEK, self.now) self.assertEqual(query.date_from(), parser.isoparse("2021-08-23T00:00:00Z")) def test_date_from_week_interval_starting_sunday(self): self.team.week_start_day = WeekStartDay.SUNDAY - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.week, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.WEEK, self.now) self.assertEqual(query.date_from(), parser.isoparse("2021-08-22T00:00:00Z")) def test_date_to_day_interval(self): - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.day, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.DAY, self.now) self.assertEqual(query.date_to(), parser.isoparse("2021-08-26T00:00:00Z")) def test_date_to_hour_interval(self): - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.hour, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.HOUR, self.now) self.assertEqual(query.date_to(), parser.isoparse("2021-08-25T01:00:00Z")) def test_get_start_of_interval_hogql_day_interval(self): - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.day, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.DAY, self.now) expected_expr = ast.Call(name="toStartOfDay", args=[ast.Constant(value=query.date_from())]) self.assertEqual(query.get_start_of_interval_hogql(), expected_expr) def test_get_start_of_interval_hogql_hour_interval(self): - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.hour, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.HOUR, self.now) expected_expr = ast.Call(name="toStartOfHour", args=[ast.Constant(value=query.date_from())]) self.assertEqual(query.get_start_of_interval_hogql(), expected_expr) def test_get_start_of_interval_hogql_week_interval(self): self.team.week_start_day = WeekStartDay.MONDAY - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.week, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.WEEK, self.now) week_mode = WeekStartDay(self.team.week_start_day or 0).clickhouse_mode expected_expr = ast.Call( name="toStartOfWeek", args=[ast.Constant(value=query.date_from()), ast.Constant(value=int(week_mode))] @@ -183,6 +183,6 @@ def test_get_start_of_interval_hogql_week_interval(self): def test_get_start_of_interval_hogql_with_source(self): source_expr = ast.Constant(value="2021-08-25T00:00:00.000Z") - query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.day, self.now) + query = QueryDateRangeWithIntervals(None, 1, self.team, IntervalType.DAY, self.now) expected_expr = ast.Call(name="toStartOfDay", args=[source_expr]) self.assertEqual(query.get_start_of_interval_hogql(source=source_expr), expected_expr) diff --git a/posthog/hogql_queries/web_analytics/stats_table.py b/posthog/hogql_queries/web_analytics/stats_table.py index 0b224932b7d76f..92521454bdb7fd 100644 --- a/posthog/hogql_queries/web_analytics/stats_table.py +++ b/posthog/hogql_queries/web_analytics/stats_table.py @@ -38,12 +38,12 @@ def __init__(self, *args, **kwargs): ) def to_query(self) -> ast.SelectQuery: - if self.query.breakdownBy == WebStatsBreakdown.Page: + if self.query.breakdownBy == WebStatsBreakdown.PAGE: if self.query.includeScrollDepth and self.query.includeBounceRate: return self.to_path_scroll_bounce_query() elif self.query.includeBounceRate: return self.to_path_bounce_query() - if self.query.breakdownBy == WebStatsBreakdown.InitialPage: + if self.query.breakdownBy == WebStatsBreakdown.INITIAL_PAGE: if self.query.includeBounceRate: return self.to_entry_bounce_query() if self._has_session_properties(): @@ -178,7 +178,7 @@ def to_entry_bounce_query(self) -> ast.SelectQuery: return query def to_path_scroll_bounce_query(self) -> ast.SelectQuery: - if self.query.breakdownBy != WebStatsBreakdown.Page: + if self.query.breakdownBy != WebStatsBreakdown.PAGE: raise NotImplementedError("Scroll depth is only supported for page breakdowns") with self.timings.measure("stats_table_bounce_query"): @@ -290,7 +290,7 @@ def to_path_scroll_bounce_query(self) -> ast.SelectQuery: return query def to_path_bounce_query(self) -> ast.SelectQuery: - if self.query.breakdownBy not in [WebStatsBreakdown.InitialPage, WebStatsBreakdown.Page]: + if self.query.breakdownBy not in [WebStatsBreakdown.INITIAL_PAGE, WebStatsBreakdown.PAGE]: raise NotImplementedError("Bounce rate is only supported for page breakdowns") with self.timings.measure("stats_table_scroll_query"): @@ -394,15 +394,15 @@ def _has_session_properties(self) -> bool: return any( get_property_type(p) == "session" for p in self.query.properties + self._test_account_filters ) or self.query.breakdownBy in { - WebStatsBreakdown.InitialChannelType, - WebStatsBreakdown.InitialReferringDomain, - WebStatsBreakdown.InitialUTMSource, - WebStatsBreakdown.InitialUTMCampaign, - WebStatsBreakdown.InitialUTMMedium, - WebStatsBreakdown.InitialUTMTerm, - WebStatsBreakdown.InitialUTMContent, - WebStatsBreakdown.InitialPage, - WebStatsBreakdown.ExitPage, + WebStatsBreakdown.INITIAL_CHANNEL_TYPE, + WebStatsBreakdown.INITIAL_REFERRING_DOMAIN, + WebStatsBreakdown.INITIAL_UTM_SOURCE, + WebStatsBreakdown.INITIAL_UTM_CAMPAIGN, + WebStatsBreakdown.INITIAL_UTM_MEDIUM, + WebStatsBreakdown.INITIAL_UTM_TERM, + WebStatsBreakdown.INITIAL_UTM_CONTENT, + WebStatsBreakdown.INITIAL_PAGE, + WebStatsBreakdown.EXIT_PAGE, } def _session_properties(self) -> ast.Expr: @@ -453,60 +453,60 @@ def calculate(self): def _counts_breakdown_value(self): match self.query.breakdownBy: - case WebStatsBreakdown.Page: + case WebStatsBreakdown.PAGE: return self._apply_path_cleaning(ast.Field(chain=["events", "properties", "$pathname"])) - case WebStatsBreakdown.InitialPage: + case WebStatsBreakdown.INITIAL_PAGE: return self._apply_path_cleaning(ast.Field(chain=["sessions", "$entry_pathname"])) - case WebStatsBreakdown.ExitPage: + case WebStatsBreakdown.EXIT_PAGE: return self._apply_path_cleaning(ast.Field(chain=["sessions", "$exit_pathname"])) - case WebStatsBreakdown.InitialReferringDomain: + case WebStatsBreakdown.INITIAL_REFERRING_DOMAIN: return ast.Field(chain=["sessions", "$entry_referring_domain"]) - case WebStatsBreakdown.InitialUTMSource: + case WebStatsBreakdown.INITIAL_UTM_SOURCE: return ast.Field(chain=["sessions", "$entry_utm_source"]) - case WebStatsBreakdown.InitialUTMCampaign: + case WebStatsBreakdown.INITIAL_UTM_CAMPAIGN: return ast.Field(chain=["sessions", "$entry_utm_campaign"]) - case WebStatsBreakdown.InitialUTMMedium: + case WebStatsBreakdown.INITIAL_UTM_MEDIUM: return ast.Field(chain=["sessions", "$entry_utm_medium"]) - case WebStatsBreakdown.InitialUTMTerm: + case WebStatsBreakdown.INITIAL_UTM_TERM: return ast.Field(chain=["sessions", "$entry_utm_term"]) - case WebStatsBreakdown.InitialUTMContent: + case WebStatsBreakdown.INITIAL_UTM_CONTENT: return ast.Field(chain=["sessions", "$entry_utm_content"]) - case WebStatsBreakdown.InitialChannelType: + case WebStatsBreakdown.INITIAL_CHANNEL_TYPE: return ast.Field(chain=["sessions", "$channel_type"]) - case WebStatsBreakdown.Browser: + case WebStatsBreakdown.BROWSER: return ast.Field(chain=["properties", "$browser"]) case WebStatsBreakdown.OS: return ast.Field(chain=["properties", "$os"]) - case WebStatsBreakdown.DeviceType: + case WebStatsBreakdown.DEVICE_TYPE: return ast.Field(chain=["properties", "$device_type"]) - case WebStatsBreakdown.Country: + case WebStatsBreakdown.COUNTRY: return ast.Field(chain=["properties", "$geoip_country_code"]) - case WebStatsBreakdown.Region: + case WebStatsBreakdown.REGION: return parse_expr( "tuple(properties.$geoip_country_code, properties.$geoip_subdivision_1_code, properties.$geoip_subdivision_1_name)" ) - case WebStatsBreakdown.City: + case WebStatsBreakdown.CITY: return parse_expr("tuple(properties.$geoip_country_code, properties.$geoip_city_name)") case _: raise NotImplementedError("Breakdown not implemented") def where_breakdown(self): match self.query.breakdownBy: - case WebStatsBreakdown.Region: + case WebStatsBreakdown.REGION: return parse_expr("tupleElement(breakdown_value, 2) IS NOT NULL") - case WebStatsBreakdown.City: + case WebStatsBreakdown.CITY: return parse_expr("tupleElement(breakdown_value, 2) IS NOT NULL") - case WebStatsBreakdown.InitialChannelType: + case WebStatsBreakdown.INITIAL_CHANNEL_TYPE: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMSource: + case WebStatsBreakdown.INITIAL_UTM_SOURCE: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMCampaign: + case WebStatsBreakdown.INITIAL_UTM_CAMPAIGN: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMMedium: + case WebStatsBreakdown.INITIAL_UTM_MEDIUM: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMTerm: + case WebStatsBreakdown.INITIAL_UTM_TERM: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMContent: + case WebStatsBreakdown.INITIAL_UTM_CONTENT: return parse_expr("TRUE") # actually show null values case _: return parse_expr("breakdown_value IS NOT NULL") diff --git a/posthog/hogql_queries/web_analytics/stats_table_legacy.py b/posthog/hogql_queries/web_analytics/stats_table_legacy.py index edb72c39ae92e8..5cb6a2a3c0889f 100644 --- a/posthog/hogql_queries/web_analytics/stats_table_legacy.py +++ b/posthog/hogql_queries/web_analytics/stats_table_legacy.py @@ -71,7 +71,7 @@ def _scroll_depth_subquery(self): def to_query(self) -> ast.SelectQuery: # special case for channel, as some hogql features to use the general code are still being worked on - if self.query.breakdownBy == WebStatsBreakdown.InitialChannelType: + if self.query.breakdownBy == WebStatsBreakdown.INITIAL_CHANNEL_TYPE: query = self.to_channel_query() elif self.query.includeScrollDepth: query = parse_select( @@ -193,51 +193,51 @@ def calculate(self): def counts_breakdown(self): match self.query.breakdownBy: - case WebStatsBreakdown.Page: + case WebStatsBreakdown.PAGE: return self._apply_path_cleaning(ast.Field(chain=["properties", "$pathname"])) - case WebStatsBreakdown.InitialChannelType: + case WebStatsBreakdown.INITIAL_CHANNEL_TYPE: raise NotImplementedError("Breakdown InitialChannelType not implemented") - case WebStatsBreakdown.InitialPage: + case WebStatsBreakdown.INITIAL_PAGE: return self._apply_path_cleaning(ast.Field(chain=["person", "properties", "$initial_pathname"])) - case WebStatsBreakdown.InitialReferringDomain: + case WebStatsBreakdown.INITIAL_REFERRING_DOMAIN: return ast.Field(chain=["person", "properties", "$initial_referring_domain"]) - case WebStatsBreakdown.InitialUTMSource: + case WebStatsBreakdown.INITIAL_UTM_SOURCE: return ast.Field(chain=["person", "properties", "$initial_utm_source"]) - case WebStatsBreakdown.InitialUTMCampaign: + case WebStatsBreakdown.INITIAL_UTM_CAMPAIGN: return ast.Field(chain=["person", "properties", "$initial_utm_campaign"]) - case WebStatsBreakdown.InitialUTMMedium: + case WebStatsBreakdown.INITIAL_UTM_MEDIUM: return ast.Field(chain=["person", "properties", "$initial_utm_medium"]) - case WebStatsBreakdown.InitialUTMTerm: + case WebStatsBreakdown.INITIAL_UTM_TERM: return ast.Field(chain=["person", "properties", "$initial_utm_term"]) - case WebStatsBreakdown.InitialUTMContent: + case WebStatsBreakdown.INITIAL_UTM_CONTENT: return ast.Field(chain=["person", "properties", "$initial_utm_content"]) - case WebStatsBreakdown.Browser: + case WebStatsBreakdown.BROWSER: return ast.Field(chain=["properties", "$browser"]) case WebStatsBreakdown.OS: return ast.Field(chain=["properties", "$os"]) - case WebStatsBreakdown.DeviceType: + case WebStatsBreakdown.DEVICE_TYPE: return ast.Field(chain=["properties", "$device_type"]) - case WebStatsBreakdown.Country: + case WebStatsBreakdown.COUNTRY: return ast.Field(chain=["properties", "$geoip_country_code"]) - case WebStatsBreakdown.Region: + case WebStatsBreakdown.REGION: return parse_expr( "tuple(properties.$geoip_country_code, properties.$geoip_subdivision_1_code, properties.$geoip_subdivision_1_name)" ) - case WebStatsBreakdown.City: + case WebStatsBreakdown.CITY: return parse_expr("tuple(properties.$geoip_country_code, properties.$geoip_city_name)") case _: raise NotImplementedError("Breakdown not implemented") def bounce_breakdown(self): match self.query.breakdownBy: - case WebStatsBreakdown.Page: + case WebStatsBreakdown.PAGE: # use initial pathname for bounce rate return self._apply_path_cleaning( ast.Call(name="any", args=[ast.Field(chain=["person", "properties", "$initial_pathname"])]) ) - case WebStatsBreakdown.InitialChannelType: + case WebStatsBreakdown.INITIAL_CHANNEL_TYPE: raise NotImplementedError("Breakdown InitialChannelType not implemented") - case WebStatsBreakdown.InitialPage: + case WebStatsBreakdown.INITIAL_PAGE: return self._apply_path_cleaning( ast.Call(name="any", args=[ast.Field(chain=["person", "properties", "$initial_pathname"])]) ) @@ -246,21 +246,21 @@ def bounce_breakdown(self): def where_breakdown(self): match self.query.breakdownBy: - case WebStatsBreakdown.Region: + case WebStatsBreakdown.REGION: return parse_expr('tupleElement("context.columns.breakdown_value", 2) IS NOT NULL') - case WebStatsBreakdown.City: + case WebStatsBreakdown.CITY: return parse_expr('tupleElement("context.columns.breakdown_value", 2) IS NOT NULL') - case WebStatsBreakdown.InitialChannelType: + case WebStatsBreakdown.INITIAL_CHANNEL_TYPE: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMSource: + case WebStatsBreakdown.INITIAL_UTM_SOURCE: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMCampaign: + case WebStatsBreakdown.INITIAL_UTM_CAMPAIGN: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMMedium: + case WebStatsBreakdown.INITIAL_UTM_MEDIUM: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMTerm: + case WebStatsBreakdown.INITIAL_UTM_TERM: return parse_expr("TRUE") # actually show null values - case WebStatsBreakdown.InitialUTMContent: + case WebStatsBreakdown.INITIAL_UTM_CONTENT: return parse_expr("TRUE") # actually show null values case _: return parse_expr('"context.columns.breakdown_value" IS NOT NULL') diff --git a/posthog/hogql_queries/web_analytics/test/test_web_analytics_query_runner.py b/posthog/hogql_queries/web_analytics/test/test_web_analytics_query_runner.py index 3ea217606522c3..87763dc288b611 100644 --- a/posthog/hogql_queries/web_analytics/test/test_web_analytics_query_runner.py +++ b/posthog/hogql_queries/web_analytics/test/test_web_analytics_query_runner.py @@ -48,7 +48,7 @@ def _create_events(self, data, event="$pageview"): ) return person_result - def _create_web_stats_table_query(self, date_from, date_to, properties, breakdown_by=WebStatsBreakdown.Page): + def _create_web_stats_table_query(self, date_from, date_to, properties, breakdown_by=WebStatsBreakdown.PAGE): query = WebStatsTableQuery( dateRange=DateRange(date_from=date_from, date_to=date_to), properties=properties, breakdownBy=breakdown_by ) @@ -63,8 +63,8 @@ def _create__web_overview_query(self, date_from, date_to, properties): def test_sample_rate_cache_key_is_same_across_subclasses(self): properties: list[Union[EventPropertyFilter, PersonPropertyFilter]] = [ - EventPropertyFilter(key="$current_url", value="/a", operator=PropertyOperator.is_not), - PersonPropertyFilter(key="$initial_utm_source", value="google", operator=PropertyOperator.is_not), + EventPropertyFilter(key="$current_url", value="/a", operator=PropertyOperator.IS_NOT), + PersonPropertyFilter(key="$initial_utm_source", value="google", operator=PropertyOperator.IS_NOT), ] date_from = "2023-12-08" date_to = "2023-12-15" @@ -76,10 +76,10 @@ def test_sample_rate_cache_key_is_same_across_subclasses(self): def test_sample_rate_cache_key_is_same_with_different_properties(self): properties_a: list[Union[EventPropertyFilter, PersonPropertyFilter]] = [ - EventPropertyFilter(key="$current_url", value="/a", operator=PropertyOperator.is_not), + EventPropertyFilter(key="$current_url", value="/a", operator=PropertyOperator.IS_NOT), ] properties_b: list[Union[EventPropertyFilter, PersonPropertyFilter]] = [ - EventPropertyFilter(key="$current_url", value="/b", operator=PropertyOperator.is_not), + EventPropertyFilter(key="$current_url", value="/b", operator=PropertyOperator.IS_NOT), ] date_from = "2023-12-08" date_to = "2023-12-15" @@ -91,7 +91,7 @@ def test_sample_rate_cache_key_is_same_with_different_properties(self): def test_sample_rate_cache_key_changes_with_date_range(self): properties: list[Union[EventPropertyFilter, PersonPropertyFilter]] = [ - EventPropertyFilter(key="$current_url", value="/a", operator=PropertyOperator.is_not), + EventPropertyFilter(key="$current_url", value="/a", operator=PropertyOperator.IS_NOT), ] date_from_a = "2023-12-08" date_from_b = "2023-12-09" diff --git a/posthog/hogql_queries/web_analytics/test/test_web_stats_table.py b/posthog/hogql_queries/web_analytics/test/test_web_stats_table.py index b753abdad0f64f..c78010569a60b6 100644 --- a/posthog/hogql_queries/web_analytics/test/test_web_stats_table.py +++ b/posthog/hogql_queries/web_analytics/test/test_web_stats_table.py @@ -93,7 +93,7 @@ def _run_web_stats_table_query( self, date_from, date_to, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, limit=None, path_cleaning_filters=None, use_sessions_table=True, @@ -192,7 +192,7 @@ def test_breakdown_channel_type_doesnt_throw(self, use_sessions_table): results = self._run_web_stats_table_query( "2023-12-01", "2023-12-03", - breakdown_by=WebStatsBreakdown.InitialChannelType, + breakdown_by=WebStatsBreakdown.INITIAL_CHANNEL_TYPE, use_sessions_table=use_sessions_table, ).results @@ -279,7 +279,7 @@ def test_scroll_depth_bounce_rate_one_user(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_scroll_depth=True, include_bounce_rate=True, ).results @@ -322,7 +322,7 @@ def test_scroll_depth_bounce_rate(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_scroll_depth=True, include_bounce_rate=True, ).results @@ -365,10 +365,10 @@ def test_scroll_depth_bounce_rate_with_filter(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_scroll_depth=True, include_bounce_rate=True, - properties=[EventPropertyFilter(key="$pathname", operator=PropertyOperator.exact, value="/a")], + properties=[EventPropertyFilter(key="$pathname", operator=PropertyOperator.EXACT, value="/a")], ).results self.assertEqual( @@ -392,7 +392,7 @@ def test_scroll_depth_bounce_rate_path_cleaning(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_scroll_depth=True, include_bounce_rate=True, path_cleaning_filters=[ @@ -425,7 +425,7 @@ def test_bounce_rate_one_user(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_bounce_rate=True, ).results @@ -467,7 +467,7 @@ def test_bounce_rate(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_bounce_rate=True, ).results @@ -509,9 +509,9 @@ def test_bounce_rate_with_property(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_bounce_rate=True, - properties=[EventPropertyFilter(key="$pathname", operator=PropertyOperator.exact, value="/a")], + properties=[EventPropertyFilter(key="$pathname", operator=PropertyOperator.EXACT, value="/a")], ).results self.assertEqual( @@ -535,7 +535,7 @@ def test_bounce_rate_path_cleaning(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.Page, + breakdown_by=WebStatsBreakdown.PAGE, include_bounce_rate=True, path_cleaning_filters=[ {"regex": "\\/a\\/\\d+", "alias": "/a/:id"}, @@ -567,7 +567,7 @@ def test_entry_bounce_rate_one_user(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.InitialPage, + breakdown_by=WebStatsBreakdown.INITIAL_PAGE, include_bounce_rate=True, ).results @@ -607,7 +607,7 @@ def test_entry_bounce_rate(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.InitialPage, + breakdown_by=WebStatsBreakdown.INITIAL_PAGE, include_bounce_rate=True, ).results @@ -647,9 +647,9 @@ def test_entry_bounce_rate_with_property(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.InitialPage, + breakdown_by=WebStatsBreakdown.INITIAL_PAGE, include_bounce_rate=True, - properties=[EventPropertyFilter(key="$pathname", operator=PropertyOperator.exact, value="/a")], + properties=[EventPropertyFilter(key="$pathname", operator=PropertyOperator.EXACT, value="/a")], ).results self.assertEqual( @@ -673,7 +673,7 @@ def test_entry_bounce_rate_path_cleaning(self): "all", "2023-12-15", use_sessions_table=True, - breakdown_by=WebStatsBreakdown.InitialPage, + breakdown_by=WebStatsBreakdown.INITIAL_PAGE, include_bounce_rate=True, path_cleaning_filters=[ {"regex": "\\/a\\/\\d+", "alias": "/a/:id"}, diff --git a/posthog/management/commands/compare_hogql_insights.py b/posthog/management/commands/compare_hogql_insights.py index 44bab4f6e71273..9a49af107e0638 100644 --- a/posthog/management/commands/compare_hogql_insights.py +++ b/posthog/management/commands/compare_hogql_insights.py @@ -38,7 +38,9 @@ def handle(self, *args, **options): if event.get("math") in ("median", "p90", "p95", "p99"): event["math"] = "sum" try: - print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") # noqa: T201 + print( # noqa: T201 + "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" + ) insight_type = insight.filters.get("insight") print( # noqa: T201 f"Checking {insight_type} Insight {insight.id} {insight.short_id} - {insight.name} " @@ -58,7 +60,7 @@ def handle(self, *args, **options): continue try: query = filter_to_query(insight.filters) - modifiers = HogQLQueryModifiers(materializationMode=MaterializationMode.legacy_null_as_string) + modifiers = HogQLQueryModifiers(materializationMode=MaterializationMode.LEGACY_NULL_AS_STRING) query_runner = get_query_runner(query, insight.team, modifiers=modifiers) hogql_results = cast(HogQLQueryResponse, query_runner.calculate()).results or [] except Exception as e: diff --git a/posthog/models/team/team.py b/posthog/models/team/team.py index 382431171680e8..e06104ce1c2a63 100644 --- a/posthog/models/team/team.py +++ b/posthog/models/team/team.py @@ -302,31 +302,31 @@ def default_modifiers(self) -> dict: @property def person_on_events_mode(self) -> PersonsOnEventsMode: if self._person_on_events_person_id_override_properties_on_events: - tag_queries(person_on_events_mode=PersonsOnEventsMode.person_id_override_properties_on_events) - return PersonsOnEventsMode.person_id_override_properties_on_events + tag_queries(person_on_events_mode=PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS) + return PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS if self._person_on_events_person_id_no_override_properties_on_events: # also tag person_on_events_enabled for legacy compatibility tag_queries( person_on_events_enabled=True, - person_on_events_mode=PersonsOnEventsMode.person_id_no_override_properties_on_events, + person_on_events_mode=PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS, ) - return PersonsOnEventsMode.person_id_no_override_properties_on_events + return PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS if self._person_on_events_person_id_override_properties_joined: tag_queries( person_on_events_enabled=True, - person_on_events_mode=PersonsOnEventsMode.person_id_override_properties_joined, + person_on_events_mode=PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED, ) - return PersonsOnEventsMode.person_id_override_properties_joined + return PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_JOINED - return PersonsOnEventsMode.disabled + return PersonsOnEventsMode.DISABLED # KLUDGE: DO NOT REFERENCE IN THE BACKEND! # Keeping this property for now only to be used by the frontend in certain cases @property def person_on_events_querying_enabled(self) -> bool: - return self.person_on_events_mode != PersonsOnEventsMode.disabled + return self.person_on_events_mode != PersonsOnEventsMode.DISABLED @property def _person_on_events_person_id_no_override_properties_on_events(self) -> bool: diff --git a/posthog/queries/breakdown_props.py b/posthog/queries/breakdown_props.py index 767985e261109a..23f4b0d51ddc47 100644 --- a/posthog/queries/breakdown_props.py +++ b/posthog/queries/breakdown_props.py @@ -86,7 +86,7 @@ def get_breakdown_prop_values( sessions_join_params: dict = {} null_person_filter = ( - f"AND notEmpty(e.person_id)" if team.person_on_events_mode != PersonsOnEventsMode.disabled else "" + f"AND notEmpty(e.person_id)" if team.person_on_events_mode != PersonsOnEventsMode.DISABLED else "" ) if person_properties_mode == PersonPropertiesMode.DIRECT_ON_EVENTS: @@ -277,12 +277,14 @@ def _to_value_expression( table="events" if direct_on_events else "groups", property_name=cast(str, breakdown), var="%(key)s", - column=f"group{breakdown_group_type_index}_properties" - if direct_on_events - else f"group_properties_{breakdown_group_type_index}", - materialised_table_column=f"group{breakdown_group_type_index}_properties" - if direct_on_events - else "group_properties", + column=( + f"group{breakdown_group_type_index}_properties" + if direct_on_events + else f"group_properties_{breakdown_group_type_index}" + ), + materialised_table_column=( + f"group{breakdown_group_type_index}_properties" if direct_on_events else "group_properties" + ), ) elif breakdown_type == "hogql": from posthog.hogql.hogql import translate_hogql diff --git a/posthog/queries/event_query/event_query.py b/posthog/queries/event_query/event_query.py index cc514c676f817b..d8816634d6ac12 100644 --- a/posthog/queries/event_query/event_query.py +++ b/posthog/queries/event_query/event_query.py @@ -64,7 +64,7 @@ def __init__( extra_event_properties: Optional[list[PropertyName]] = None, extra_person_fields: Optional[list[ColumnName]] = None, override_aggregate_users_by_distinct_id: Optional[bool] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, **kwargs, ) -> None: if extra_person_fields is None: @@ -126,9 +126,9 @@ def _determine_should_join_distinct_ids(self) -> None: pass def _get_person_id_alias(self, person_on_events_mode) -> str: - if person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: return f"if(notEmpty({self.PERSON_ID_OVERRIDES_TABLE_ALIAS}.distinct_id), {self.PERSON_ID_OVERRIDES_TABLE_ALIAS}.person_id, {self.EVENT_TABLE_ALIAS}.person_id)" - elif person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + elif person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: return f"{self.EVENT_TABLE_ALIAS}.person_id" return f"{self.DISTINCT_ID_TABLE_ALIAS}.person_id" @@ -137,7 +137,7 @@ def _get_person_ids_query(self, *, relevant_events_conditions: str = "") -> str: if not self._should_join_distinct_ids: return "" - if self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: return PERSON_DISTINCT_ID_OVERRIDES_JOIN_SQL.format( person_overrides_table_alias=self.PERSON_ID_OVERRIDES_TABLE_ALIAS, event_table_alias=self.EVENT_TABLE_ALIAS, diff --git a/posthog/queries/foss_cohort_query.py b/posthog/queries/foss_cohort_query.py index e801008c447269..d4925856afd948 100644 --- a/posthog/queries/foss_cohort_query.py +++ b/posthog/queries/foss_cohort_query.py @@ -191,9 +191,11 @@ def _unwrap(property_group: PropertyGroup, negate_group: bool = False) -> Proper ) else: return PropertyGroup( - type=PropertyOperatorType.AND - if property_group.type == PropertyOperatorType.OR - else PropertyOperatorType.OR, + type=( + PropertyOperatorType.AND + if property_group.type == PropertyOperatorType.OR + else PropertyOperatorType.OR + ), values=[_unwrap(v, True) for v in cast(list[PropertyGroup], property_group.values)], ) @@ -246,9 +248,11 @@ def _unwrap(property_group: PropertyGroup, negate_group: bool = False) -> Proper return PropertyGroup(type=property_group.type, values=new_property_group_list) else: return PropertyGroup( - type=PropertyOperatorType.AND - if property_group.type == PropertyOperatorType.OR - else PropertyOperatorType.OR, + type=( + PropertyOperatorType.AND + if property_group.type == PropertyOperatorType.OR + else PropertyOperatorType.OR + ), values=new_property_group_list, ) @@ -306,7 +310,7 @@ def _build_sources(self, subq: list[tuple[str, str]]) -> tuple[str, str]: fields = f"{subq_alias}.person_id" elif prev_alias: # can't join without a previous alias if subq_alias == self.PERSON_TABLE_ALIAS and self.should_pushdown_persons: - if self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: # when using person-on-events, instead of inner join, we filter inside # the event query itself continue @@ -337,11 +341,11 @@ def _get_behavior_subquery(self) -> tuple[str, dict[str, Any], str]: query, params = "", {} if self._should_join_behavioral_query: _fields = [ - f"{self.DISTINCT_ID_TABLE_ALIAS if self._person_on_events_mode == PersonsOnEventsMode.disabled else self.EVENT_TABLE_ALIAS}.person_id AS person_id" + f"{self.DISTINCT_ID_TABLE_ALIAS if self._person_on_events_mode == PersonsOnEventsMode.DISABLED else self.EVENT_TABLE_ALIAS}.person_id AS person_id" ] _fields.extend(self._fields) - if self.should_pushdown_persons and self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self.should_pushdown_persons and self._person_on_events_mode != PersonsOnEventsMode.DISABLED: person_prop_query, person_prop_params = self._get_prop_groups( self._inner_property_groups, person_properties_mode=PersonPropertiesMode.DIRECT_ON_EVENTS, @@ -557,7 +561,7 @@ def get_performed_event_multiple(self, prop: Property, prepend: str, idx: int) - def _determine_should_join_distinct_ids(self) -> None: self._should_join_distinct_ids = ( - self._person_on_events_mode != PersonsOnEventsMode.person_id_no_override_properties_on_events + self._person_on_events_mode != PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS ) def _determine_should_join_persons(self) -> None: diff --git a/posthog/queries/funnels/base.py b/posthog/queries/funnels/base.py index a6de14b050cfa6..30265cace41e33 100644 --- a/posthog/queries/funnels/base.py +++ b/posthog/queries/funnels/base.py @@ -228,9 +228,11 @@ def _format_single_funnel(self, results, with_breakdown=False): # breakdown_value will return the underlying id if different from display ready value (ex: cohort id) serialized_result.update( { - "breakdown": get_breakdown_cohort_name(breakdown_value) - if self._filter.breakdown_type == "cohort" - else breakdown_value, + "breakdown": ( + get_breakdown_cohort_name(breakdown_value) + if self._filter.breakdown_type == "cohort" + else breakdown_value + ), "breakdown_value": breakdown_value, } ) @@ -728,7 +730,7 @@ def _get_breakdown_select_prop(self) -> tuple[str, dict[str, Any]]: self.params.update({"breakdown": self._filter.breakdown}) if self._filter.breakdown_type == "person": - if self._team.person_on_events_mode != PersonsOnEventsMode.disabled: + if self._team.person_on_events_mode != PersonsOnEventsMode.DISABLED: basic_prop_selector, basic_prop_params = get_single_or_multi_property_string_expr( self._filter.breakdown, table="events", @@ -758,7 +760,7 @@ def _get_breakdown_select_prop(self) -> tuple[str, dict[str, Any]]: # :TRICKY: We only support string breakdown for group properties assert isinstance(self._filter.breakdown, str) - if self._team.person_on_events_mode != PersonsOnEventsMode.disabled and groups_on_events_querying_enabled(): + if self._team.person_on_events_mode != PersonsOnEventsMode.DISABLED and groups_on_events_querying_enabled(): properties_field = f"group{self._filter.breakdown_group_type_index}_properties" expression, _ = get_property_string_expr( table="events", diff --git a/posthog/queries/funnels/funnel_event_query.py b/posthog/queries/funnels/funnel_event_query.py index a814f9cb548011..a99f456a33eeae 100644 --- a/posthog/queries/funnels/funnel_event_query.py +++ b/posthog/queries/funnels/funnel_event_query.py @@ -49,7 +49,7 @@ def get_query( _fields += [f"{self.EVENT_TABLE_ALIAS}.{field} AS {field}" for field in self._extra_fields] - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: _fields += [f"{self._person_id_alias} as person_id"] _fields.extend( @@ -95,7 +95,7 @@ def get_query( null_person_filter = ( f"AND notEmpty({self.EVENT_TABLE_ALIAS}.person_id)" - if self._person_on_events_mode != PersonsOnEventsMode.disabled + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED else "" ) @@ -131,9 +131,9 @@ def _determine_should_join_distinct_ids(self) -> None: ) is_using_cohort_propertes = self._column_optimizer.is_using_cohort_propertes - if self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: self._should_join_distinct_ids = True - elif self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events or ( + elif self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS or ( non_person_id_aggregation and not is_using_cohort_propertes ): self._should_join_distinct_ids = False @@ -142,7 +142,7 @@ def _determine_should_join_distinct_ids(self) -> None: def _determine_should_join_persons(self) -> None: EventQuery._determine_should_join_persons(self) - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: self._should_join_persons = False def _get_entity_query(self, entities=None, entity_name="events") -> tuple[str, dict[str, Any]]: diff --git a/posthog/queries/groups_join_query/groups_join_query.py b/posthog/queries/groups_join_query/groups_join_query.py index 6499d39ce1e945..128398584a352c 100644 --- a/posthog/queries/groups_join_query/groups_join_query.py +++ b/posthog/queries/groups_join_query/groups_join_query.py @@ -23,7 +23,7 @@ def __init__( team_id: int, column_optimizer: Optional[ColumnOptimizer] = None, join_key: Optional[str] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, ) -> None: self._filter = filter self._team_id = team_id diff --git a/posthog/queries/paths/paths_event_query.py b/posthog/queries/paths/paths_event_query.py index 31241cea649192..6380c43aae72ca 100644 --- a/posthog/queries/paths/paths_event_query.py +++ b/posthog/queries/paths/paths_event_query.py @@ -116,7 +116,7 @@ def get_query(self) -> tuple[str, dict[str, Any]]: null_person_filter = ( f"AND notEmpty({self.EVENT_TABLE_ALIAS}.person_id)" - if self._person_on_events_mode != PersonsOnEventsMode.disabled + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED else "" ) @@ -141,14 +141,14 @@ def get_query(self) -> tuple[str, dict[str, Any]]: return query, self.params def _determine_should_join_distinct_ids(self) -> None: - if self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: self._should_join_distinct_ids = False else: self._should_join_distinct_ids = True def _determine_should_join_persons(self) -> None: EventQuery._determine_should_join_persons(self) - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: self._should_join_persons = False def _get_grouping_fields(self) -> tuple[list[str], dict[str, Any]]: diff --git a/posthog/queries/retention/retention.py b/posthog/queries/retention/retention.py index d3b9f43ca5c60e..5d0779071ffe41 100644 --- a/posthog/queries/retention/retention.py +++ b/posthog/queries/retention/retention.py @@ -166,7 +166,7 @@ def build_returning_event_query( filter: RetentionFilter, team: Team, aggregate_users_by_distinct_id: Optional[bool] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, retention_events_query=RetentionEventsQuery, ) -> tuple[str, dict[str, Any]]: returning_event_query_templated, returning_event_params = retention_events_query( @@ -184,7 +184,7 @@ def build_target_event_query( filter: RetentionFilter, team: Team, aggregate_users_by_distinct_id: Optional[bool] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, retention_events_query=RetentionEventsQuery, ) -> tuple[str, dict[str, Any]]: target_event_query_templated, target_event_params = retention_events_query( diff --git a/posthog/queries/retention/retention_events_query.py b/posthog/queries/retention/retention_events_query.py index 9e64b758be6e84..ed9b45d47b5841 100644 --- a/posthog/queries/retention/retention_events_query.py +++ b/posthog/queries/retention/retention_events_query.py @@ -27,7 +27,7 @@ def __init__( event_query_type: RetentionQueryType, team: Team, aggregate_users_by_distinct_id: Optional[bool] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, ): self._event_query_type = event_query_type super().__init__( @@ -56,14 +56,14 @@ def get_query(self) -> tuple[str, dict[str, Any]]: materalised_table_column = "properties" if breakdown_type == "person": - table = "person" if self._person_on_events_mode == PersonsOnEventsMode.disabled else "events" + table = "person" if self._person_on_events_mode == PersonsOnEventsMode.DISABLED else "events" column = ( "person_props" - if self._person_on_events_mode == PersonsOnEventsMode.disabled + if self._person_on_events_mode == PersonsOnEventsMode.DISABLED else "person_properties" ) materalised_table_column = ( - "properties" if self._person_on_events_mode == PersonsOnEventsMode.disabled else "person_properties" + "properties" if self._person_on_events_mode == PersonsOnEventsMode.DISABLED else "person_properties" ) breakdown_values_expression, breakdown_values_params = get_single_or_multi_property_string_expr( @@ -134,10 +134,12 @@ def get_query(self) -> tuple[str, dict[str, Any]]: self.params.update(prop_params) entity_query, entity_params = self._get_entity_query( - entity=self._filter.target_entity - if self._event_query_type == RetentionQueryType.TARGET - or self._event_query_type == RetentionQueryType.TARGET_FIRST_TIME - else self._filter.returning_entity + entity=( + self._filter.target_entity + if self._event_query_type == RetentionQueryType.TARGET + or self._event_query_type == RetentionQueryType.TARGET_FIRST_TIME + else self._filter.returning_entity + ) ) self.params.update(entity_params) @@ -149,7 +151,7 @@ def get_query(self) -> tuple[str, dict[str, Any]]: null_person_filter = ( f"AND notEmpty({self.EVENT_TABLE_ALIAS}.person_id)" - if self._person_on_events_mode != PersonsOnEventsMode.disabled + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED else "" ) @@ -197,7 +199,7 @@ def _determine_should_join_distinct_ids(self) -> None: self._filter.aggregation_group_type_index is not None or self._aggregate_users_by_distinct_id ) is_using_cohort_propertes = self._column_optimizer.is_using_cohort_propertes - if self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events or ( + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS or ( non_person_id_aggregation and not is_using_cohort_propertes ): self._should_join_distinct_ids = False @@ -206,7 +208,7 @@ def _determine_should_join_distinct_ids(self) -> None: def _determine_should_join_persons(self) -> None: EventQuery._determine_should_join_persons(self) - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: self._should_join_persons = False def _get_entity_query(self, entity: Entity): diff --git a/posthog/queries/stickiness/stickiness_event_query.py b/posthog/queries/stickiness/stickiness_event_query.py index 7c8c92222ef951..8810bed83c6ad6 100644 --- a/posthog/queries/stickiness/stickiness_event_query.py +++ b/posthog/queries/stickiness/stickiness_event_query.py @@ -43,7 +43,7 @@ def get_query(self) -> tuple[str, dict[str, Any]]: null_person_filter = ( f"AND notEmpty({self.EVENT_TABLE_ALIAS}.person_id)" - if self._person_on_events_mode != PersonsOnEventsMode.disabled + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED else "" ) @@ -82,14 +82,14 @@ def _person_query(self): ) def _determine_should_join_distinct_ids(self) -> None: - if self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: self._should_join_distinct_ids = False else: self._should_join_distinct_ids = True def _determine_should_join_persons(self) -> None: EventQuery._determine_should_join_persons(self) - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: self._should_join_persons = False def aggregation_target(self): diff --git a/posthog/queries/trends/breakdown.py b/posthog/queries/trends/breakdown.py index fb4c7f2cca2b30..db6fd0860c38f5 100644 --- a/posthog/queries/trends/breakdown.py +++ b/posthog/queries/trends/breakdown.py @@ -98,7 +98,7 @@ def __init__( filter: Filter, team: Team, column_optimizer: Optional[ColumnOptimizer] = None, - person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.disabled, + person_on_events_mode: PersonsOnEventsMode = PersonsOnEventsMode.DISABLED, add_person_urls: bool = False, ): self.entity = entity @@ -109,9 +109,9 @@ def __init__( self.column_optimizer = column_optimizer or ColumnOptimizer(self.filter, self.team_id) self.add_person_urls = add_person_urls self.person_on_events_mode = person_on_events_mode - if person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: self._person_id_alias = f"if(notEmpty({self.PERSON_ID_OVERRIDES_TABLE_ALIAS}.distinct_id), {self.PERSON_ID_OVERRIDES_TABLE_ALIAS}.person_id, {self.EVENT_TABLE_ALIAS}.person_id)" - elif person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + elif person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: self._person_id_alias = f"{self.EVENT_TABLE_ALIAS}.person_id" else: self._person_id_alias = f"{self.DISTINCT_ID_TABLE_ALIAS}.person_id" @@ -129,7 +129,7 @@ def _props_to_filter(self) -> tuple[str, dict]: ) target_properties: Optional[PropertyGroup] = props_to_filter - if self.person_on_events_mode == PersonsOnEventsMode.disabled: + if self.person_on_events_mode == PersonsOnEventsMode.DISABLED: target_properties = self.column_optimizer.property_optimizer.parse_property_groups(props_to_filter).outer return parse_prop_grouped_clauses( @@ -160,9 +160,11 @@ def get_query(self) -> tuple[str, dict, Callable]: self.team, filter=self.filter, event_table_alias=self.EVENT_TABLE_ALIAS, - person_id_alias=f"person_id" - if self.person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events - else self._person_id_alias, + person_id_alias=( + f"person_id" + if self.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS + else self._person_id_alias + ), ) action_query = "" @@ -193,13 +195,15 @@ def get_query(self) -> tuple[str, dict, Callable]: "parsed_date_from": parsed_date_from, "parsed_date_to": parsed_date_to, "actions_query": "AND {}".format(action_query) if action_query else "", - "event_filter": "AND event = %(event)s" - if self.entity.type == TREND_FILTER_TYPE_EVENTS and self.entity.id is not None - else "", + "event_filter": ( + "AND event = %(event)s" + if self.entity.type == TREND_FILTER_TYPE_EVENTS and self.entity.id is not None + else "" + ), "filters": prop_filters, - "null_person_filter": f"AND notEmpty(e.person_id)" - if self.person_on_events_mode != PersonsOnEventsMode.disabled - else "", + "null_person_filter": ( + f"AND notEmpty(e.person_id)" if self.person_on_events_mode != PersonsOnEventsMode.DISABLED else "" + ), } _params, _breakdown_filter_params = {}, {} @@ -491,9 +495,11 @@ def _breakdown_prop_params(self, aggregate_operation: str, math_params: dict): return ( { - "values": [*values_arr, breakdown_other_value] - if has_more_values and not self.filter.breakdown_hide_other_aggregation - else values_arr, + "values": ( + [*values_arr, breakdown_other_value] + if has_more_values and not self.filter.breakdown_hide_other_aggregation + else values_arr + ), "breakdown_other_value": breakdown_other_value, "breakdown_null_value": breakdown_null_value, }, @@ -520,7 +526,7 @@ def _get_breakdown_value(self, breakdown: str) -> str: raise ValidationError(f'Invalid breakdown "{breakdown}" for breakdown type "session"') elif ( - self.person_on_events_mode != PersonsOnEventsMode.disabled + self.person_on_events_mode != PersonsOnEventsMode.DISABLED and self.filter.breakdown_type == "group" and groups_on_events_querying_enabled() ): @@ -532,7 +538,7 @@ def _get_breakdown_value(self, breakdown: str) -> str: properties_field, materialised_table_column=properties_field, ) - elif self.person_on_events_mode != PersonsOnEventsMode.disabled and self.filter.breakdown_type != "group": + elif self.person_on_events_mode != PersonsOnEventsMode.DISABLED and self.filter.breakdown_type != "group": if self.filter.breakdown_type == "person": breakdown_value, _ = get_property_string_expr( "events", @@ -626,11 +632,11 @@ def _parse(result: list) -> list: } parsed_params: dict[str, str] = encode_get_request_params({**filter_params, **extra_params}) parsed_result = { - "aggregated_value": float( - correct_result_for_sampling(aggregated_value, filter.sampling_factor, entity.math) - ) - if aggregated_value is not None - else None, + "aggregated_value": ( + float(correct_result_for_sampling(aggregated_value, filter.sampling_factor, entity.math)) + if aggregated_value is not None + else None + ), "filter": filter_params, "persons": { "filter": extra_params, @@ -746,10 +752,10 @@ def _determine_breakdown_label( return str(value) or BREAKDOWN_NULL_DISPLAY def _person_join_condition(self) -> tuple[str, dict]: - if self.person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + if self.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: return "", {} - if self.person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if self.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: return ( PERSON_DISTINCT_ID_OVERRIDES_JOIN_SQL.format( person_overrides_table_alias=self.PERSON_ID_OVERRIDES_TABLE_ALIAS, diff --git a/posthog/queries/trends/lifecycle.py b/posthog/queries/trends/lifecycle.py index 199e3c57973b6f..141df25134d1da 100644 --- a/posthog/queries/trends/lifecycle.py +++ b/posthog/queries/trends/lifecycle.py @@ -126,12 +126,12 @@ def get_query(self): self.params.update(entity_prop_params) created_at_clause = ( - "person.created_at" if self._person_on_events_mode == PersonsOnEventsMode.disabled else "person_created_at" + "person.created_at" if self._person_on_events_mode == PersonsOnEventsMode.DISABLED else "person_created_at" ) null_person_filter = ( "" - if self._person_on_events_mode == PersonsOnEventsMode.disabled + if self._person_on_events_mode == PersonsOnEventsMode.DISABLED else f"AND notEmpty({self.EVENT_TABLE_ALIAS}.person_id)" ) @@ -187,8 +187,8 @@ def _get_date_filter(self): def _determine_should_join_distinct_ids(self) -> None: self._should_join_distinct_ids = ( - self._person_on_events_mode != PersonsOnEventsMode.person_id_no_override_properties_on_events + self._person_on_events_mode != PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS ) def _determine_should_join_persons(self) -> None: - self._should_join_persons = self._person_on_events_mode == PersonsOnEventsMode.disabled + self._should_join_persons = self._person_on_events_mode == PersonsOnEventsMode.DISABLED diff --git a/posthog/queries/trends/total_volume.py b/posthog/queries/trends/total_volume.py index 5e91d9272cf18a..355b3c1d4e721c 100644 --- a/posthog/queries/trends/total_volume.py +++ b/posthog/queries/trends/total_volume.py @@ -53,9 +53,9 @@ def _total_volume_query(self, entity: Entity, filter: Filter, team: Team) -> tup interval_func = get_interval_func_ch(filter.interval) person_id_alias = f"{self.DISTINCT_ID_TABLE_ALIAS}.person_id" - if team.person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if team.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: person_id_alias = f"if(notEmpty({self.PERSON_ID_OVERRIDES_TABLE_ALIAS}.person_id), {self.PERSON_ID_OVERRIDES_TABLE_ALIAS}.person_id, {self.EVENT_TABLE_ALIAS}.person_id)" - elif team.person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + elif team.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: person_id_alias = f"{self.EVENT_TABLE_ALIAS}.person_id" aggregate_operation, join_condition, math_params = process_math( @@ -70,10 +70,12 @@ def _total_volume_query(self, entity: Entity, filter: Filter, team: Team) -> tup filter=filter, entity=entity, team=team, - should_join_distinct_ids=True - if join_condition != "" - or (entity.math in [WEEKLY_ACTIVE, MONTHLY_ACTIVE] and not team.aggregate_users_by_distinct_id) - else False, + should_join_distinct_ids=( + True + if join_condition != "" + or (entity.math in [WEEKLY_ACTIVE, MONTHLY_ACTIVE] and not team.aggregate_users_by_distinct_id) + else False + ), person_on_events_mode=team.person_on_events_mode, ) event_query_base, event_query_params = trend_event_query.get_query_base() diff --git a/posthog/queries/trends/trends_actors.py b/posthog/queries/trends/trends_actors.py index f7db8b36d8ac33..f69db981d309ad 100644 --- a/posthog/queries/trends/trends_actors.py +++ b/posthog/queries/trends/trends_actors.py @@ -61,18 +61,18 @@ def actor_query(self, limit_actors: Optional[bool] = True) -> tuple[str, dict]: value=lower_bound, operator="gte", type=self._filter.breakdown_type, - group_type_index=self._filter.breakdown_group_type_index - if self._filter.breakdown_type == "group" - else None, + group_type_index=( + self._filter.breakdown_group_type_index if self._filter.breakdown_type == "group" else None + ), ), Property( key=self._filter.breakdown, value=upper_bound, operator="lt", type=self._filter.breakdown_type, - group_type_index=self._filter.breakdown_group_type_index - if self._filter.breakdown_type == "group" - else None, + group_type_index=( + self._filter.breakdown_group_type_index if self._filter.breakdown_type == "group" else None + ), ), ] else: @@ -81,9 +81,9 @@ def actor_query(self, limit_actors: Optional[bool] = True) -> tuple[str, dict]: key=self._filter.breakdown, value=self._filter.breakdown_value, type=self._filter.breakdown_type, - group_type_index=self._filter.breakdown_group_type_index - if self._filter.breakdown_type == "group" - else None, + group_type_index=( + self._filter.breakdown_group_type_index if self._filter.breakdown_type == "group" else None + ), ) ] @@ -104,7 +104,7 @@ def actor_query(self, limit_actors: Optional[bool] = True) -> tuple[str, dict]: team=self._team, entity=self.entity, should_join_distinct_ids=not self.is_aggregating_by_groups - and self._team.person_on_events_mode != PersonsOnEventsMode.person_id_no_override_properties_on_events, + and self._team.person_on_events_mode != PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS, extra_event_properties=["$window_id", "$session_id"] if self._filter.include_recordings else [], extra_fields=extra_fields, person_on_events_mode=self._team.person_on_events_mode, diff --git a/posthog/queries/trends/trends_event_query.py b/posthog/queries/trends/trends_event_query.py index b856cb6a035e53..837a8a352a3346 100644 --- a/posthog/queries/trends/trends_event_query.py +++ b/posthog/queries/trends/trends_event_query.py @@ -10,7 +10,7 @@ def get_query(self) -> tuple[str, dict[str, Any]]: person_id_field = "" if self._should_join_distinct_ids: person_id_field = f", {self._person_id_alias} as person_id" - elif self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + elif self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: person_id_field = f", {self.EVENT_TABLE_ALIAS}.person_id as person_id" _fields = ( @@ -62,7 +62,7 @@ def get_query(self) -> tuple[str, dict[str, Any]]: return f"SELECT {_fields} {base_query}", params def _get_extra_person_columns(self) -> str: - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: return " ".join( ", {extract} as {column_name}".format( extract=get_property_string_expr( diff --git a/posthog/queries/trends/trends_event_query_base.py b/posthog/queries/trends/trends_event_query_base.py index 8fb17d3579e8f0..588307e1a7723f 100644 --- a/posthog/queries/trends/trends_event_query_base.py +++ b/posthog/queries/trends/trends_event_query_base.py @@ -80,7 +80,7 @@ def get_query_base(self) -> tuple[str, dict[str, Any]]: return query, self.params def _determine_should_join_distinct_ids(self) -> None: - if self._person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: self._should_join_distinct_ids = False is_entity_per_user = self._entity.math in ( @@ -97,7 +97,7 @@ def _determine_should_join_distinct_ids(self) -> None: self._should_join_distinct_ids = True def _determine_should_join_persons(self) -> None: - if self._person_on_events_mode != PersonsOnEventsMode.disabled: + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED: self._should_join_persons = False else: EventQuery._determine_should_join_persons(self) @@ -107,7 +107,7 @@ def _get_not_null_actor_condition(self) -> str: # If aggregating by person, exclude events with null/zero person IDs return ( f"AND notEmpty({self.EVENT_TABLE_ALIAS}.person_id)" - if self._person_on_events_mode != PersonsOnEventsMode.disabled + if self._person_on_events_mode != PersonsOnEventsMode.DISABLED else "" ) else: diff --git a/posthog/queries/trends/util.py b/posthog/queries/trends/util.py index 5f4cef63da0728..09f34ff135633b 100644 --- a/posthog/queries/trends/util.py +++ b/posthog/queries/trends/util.py @@ -176,9 +176,9 @@ def determine_aggregator(entity: Entity, team: Team) -> str: return f'"$group_{entity.math_group_type_index}"' elif team.aggregate_users_by_distinct_id: return "e.distinct_id" - elif team.person_on_events_mode == PersonsOnEventsMode.person_id_no_override_properties_on_events: + elif team.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS: return "e.person_id" - elif team.person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + elif team.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: return f"if(notEmpty(overrides.distinct_id), overrides.person_id, e.person_id)" else: return "pdi.person_id" diff --git a/posthog/queries/util.py b/posthog/queries/util.py index 5cbeea74716b02..44dac7dd8fdb95 100644 --- a/posthog/queries/util.py +++ b/posthog/queries/util.py @@ -178,10 +178,10 @@ def correct_result_for_sampling( def get_person_properties_mode(team: Team) -> PersonPropertiesMode: - if team.person_on_events_mode == PersonsOnEventsMode.disabled: + if team.person_on_events_mode == PersonsOnEventsMode.DISABLED: return PersonPropertiesMode.USING_PERSON_PROPERTIES_COLUMN - if team.person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if team.person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: return PersonPropertiesMode.DIRECT_ON_EVENTS_WITH_POE_V2 return PersonPropertiesMode.DIRECT_ON_EVENTS diff --git a/posthog/schema.py b/posthog/schema.py index ed138547bf66b7..aa1bddb107a45b 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -13,50 +13,50 @@ class SchemaRoot(RootModel[Any]): class MathGroupTypeIndex(float, Enum): - number_0 = 0 - number_1 = 1 - number_2 = 2 - number_3 = 3 - number_4 = 4 + NUMBER_0 = 0 + NUMBER_1 = 1 + NUMBER_2 = 2 + NUMBER_3 = 3 + NUMBER_4 = 4 class AggregationAxisFormat(str, Enum): - numeric = "numeric" - duration = "duration" - duration_ms = "duration_ms" - percentage = "percentage" - percentage_scaled = "percentage_scaled" + NUMERIC = "numeric" + DURATION = "duration" + DURATION_MS = "duration_ms" + PERCENTAGE = "percentage" + PERCENTAGE_SCALED = "percentage_scaled" class Kind(str, Enum): - Method = "Method" - Function = "Function" - Constructor = "Constructor" - Field = "Field" - Variable = "Variable" - Class = "Class" - Struct = "Struct" - Interface = "Interface" - Module = "Module" - Property = "Property" - Event = "Event" - Operator = "Operator" - Unit = "Unit" - Value = "Value" - Constant = "Constant" - Enum = "Enum" - EnumMember = "EnumMember" - Keyword = "Keyword" - Text = "Text" - Color = "Color" - File = "File" - Reference = "Reference" - Customcolor = "Customcolor" - Folder = "Folder" - TypeParameter = "TypeParameter" - User = "User" - Issue = "Issue" - Snippet = "Snippet" + METHOD = "Method" + FUNCTION = "Function" + CONSTRUCTOR = "Constructor" + FIELD = "Field" + VARIABLE = "Variable" + CLASS_ = "Class" + STRUCT = "Struct" + INTERFACE = "Interface" + MODULE = "Module" + PROPERTY = "Property" + EVENT = "Event" + OPERATOR = "Operator" + UNIT = "Unit" + VALUE = "Value" + CONSTANT = "Constant" + ENUM = "Enum" + ENUM_MEMBER = "EnumMember" + KEYWORD = "Keyword" + TEXT = "Text" + COLOR = "Color" + FILE = "File" + REFERENCE = "Reference" + CUSTOMCOLOR = "Customcolor" + FOLDER = "Folder" + TYPE_PARAMETER = "TypeParameter" + USER = "User" + ISSUE = "Issue" + SNIPPET = "Snippet" class AutocompleteCompletionItem(BaseModel): @@ -65,7 +65,9 @@ class AutocompleteCompletionItem(BaseModel): ) detail: Optional[str] = Field( default=None, - description="A human-readable string with additional information about this item, like type or symbol information.", + description=( + "A human-readable string with additional information about this item, like type or symbol information." + ), ) documentation: Optional[str] = Field( default=None, description="A human-readable string that represents a doc-comment." @@ -78,34 +80,37 @@ class AutocompleteCompletionItem(BaseModel): ) label: str = Field( ..., - description="The label of this completion item. By default this is also the text that is inserted when selecting this completion.", + description=( + "The label of this completion item. By default this is also the text that is inserted when selecting this" + " completion." + ), ) class BaseMathType(str, Enum): - total = "total" - dau = "dau" - weekly_active = "weekly_active" - monthly_active = "monthly_active" - unique_session = "unique_session" + TOTAL = "total" + DAU = "dau" + WEEKLY_ACTIVE = "weekly_active" + MONTHLY_ACTIVE = "monthly_active" + UNIQUE_SESSION = "unique_session" class BreakdownAttributionType(str, Enum): - first_touch = "first_touch" - last_touch = "last_touch" - all_events = "all_events" - step = "step" + FIRST_TOUCH = "first_touch" + LAST_TOUCH = "last_touch" + ALL_EVENTS = "all_events" + STEP = "step" class BreakdownType(str, Enum): - cohort = "cohort" - person = "person" - event = "event" - group = "group" - session = "session" - hogql = "hogql" - data_warehouse = "data_warehouse" - data_warehouse_person_property = "data_warehouse_person_property" + COHORT = "cohort" + PERSON = "person" + EVENT = "event" + GROUP = "group" + SESSION = "session" + HOGQL = "hogql" + DATA_WAREHOUSE = "data_warehouse" + DATA_WAREHOUSE_PERSON_PROPERTY = "data_warehouse_person_property" class BreakdownValueInt(RootModel[int]): @@ -160,15 +165,15 @@ class ChartAxis(BaseModel): class ChartDisplayType(str, Enum): - ActionsLineGraph = "ActionsLineGraph" - ActionsBar = "ActionsBar" - ActionsAreaGraph = "ActionsAreaGraph" - ActionsLineGraphCumulative = "ActionsLineGraphCumulative" - BoldNumber = "BoldNumber" - ActionsPie = "ActionsPie" - ActionsBarValue = "ActionsBarValue" - ActionsTable = "ActionsTable" - WorldMap = "WorldMap" + ACTIONS_LINE_GRAPH = "ActionsLineGraph" + ACTIONS_BAR = "ActionsBar" + ACTIONS_AREA_GRAPH = "ActionsAreaGraph" + ACTIONS_LINE_GRAPH_CUMULATIVE = "ActionsLineGraphCumulative" + BOLD_NUMBER = "BoldNumber" + ACTIONS_PIE = "ActionsPie" + ACTIONS_BAR_VALUE = "ActionsBarValue" + ACTIONS_TABLE = "ActionsTable" + WORLD_MAP = "WorldMap" class ClickhouseQueryProgress(BaseModel): @@ -193,13 +198,13 @@ class CohortPropertyFilter(BaseModel): class CountPerActorMathType(str, Enum): - avg_count_per_actor = "avg_count_per_actor" - min_count_per_actor = "min_count_per_actor" - max_count_per_actor = "max_count_per_actor" - median_count_per_actor = "median_count_per_actor" - p90_count_per_actor = "p90_count_per_actor" - p95_count_per_actor = "p95_count_per_actor" - p99_count_per_actor = "p99_count_per_actor" + AVG_COUNT_PER_ACTOR = "avg_count_per_actor" + MIN_COUNT_PER_ACTOR = "min_count_per_actor" + MAX_COUNT_PER_ACTOR = "max_count_per_actor" + MEDIAN_COUNT_PER_ACTOR = "median_count_per_actor" + P90_COUNT_PER_ACTOR = "p90_count_per_actor" + P95_COUNT_PER_ACTOR = "p95_count_per_actor" + P99_COUNT_PER_ACTOR = "p99_count_per_actor" class Response3(BaseModel): @@ -243,25 +248,25 @@ class DatabaseSchemaSource(BaseModel): class Type(str, Enum): - posthog = "posthog" - data_warehouse = "data_warehouse" - view = "view" + POSTHOG = "posthog" + DATA_WAREHOUSE = "data_warehouse" + VIEW = "view" class DatabaseSerializedFieldType(str, Enum): - integer = "integer" - float = "float" - string = "string" - datetime = "datetime" - date = "date" - boolean = "boolean" - array = "array" - json = "json" - lazy_table = "lazy_table" - virtual_table = "virtual_table" - field_traverser = "field_traverser" - expression = "expression" - view = "view" + INTEGER = "integer" + FLOAT = "float" + STRING = "string" + DATETIME = "datetime" + DATE = "date" + BOOLEAN = "boolean" + ARRAY = "array" + JSON = "json" + LAZY_TABLE = "lazy_table" + VIRTUAL_TABLE = "virtual_table" + FIELD_TRAVERSER = "field_traverser" + EXPRESSION = "expression" + VIEW = "view" class DateRange(BaseModel): @@ -272,7 +277,10 @@ class DateRange(BaseModel): date_to: Optional[str] = None explicitDate: Optional[bool] = Field( default=False, - description="Whether the date_from and date_to should be used verbatim. Disables rounding to the start and end of period.", + description=( + "Whether the date_from and date_to should be used verbatim. Disables rounding to the start and end of" + " period." + ), ) @@ -285,10 +293,10 @@ class Day(RootModel[int]): class Key(str, Enum): - tag_name = "tag_name" - text = "text" - href = "href" - selector = "selector" + TAG_NAME = "tag_name" + TEXT = "text" + HREF = "href" + SELECTOR = "selector" class ElementType(BaseModel): @@ -314,10 +322,10 @@ class EmptyPropertyFilter(BaseModel): class EntityType(str, Enum): - actions = "actions" - events = "events" - data_warehouse = "data_warehouse" - new_entity = "new_entity" + ACTIONS = "actions" + EVENTS = "events" + DATA_WAREHOUSE = "data_warehouse" + NEW_ENTITY = "new_entity" class EventDefinition(BaseModel): @@ -330,8 +338,8 @@ class EventDefinition(BaseModel): class CorrelationType(str, Enum): - success = "success" - failure = "failure" + SUCCESS = "success" + FAILURE = "failure" class EventOddsRatioSerialized(BaseModel): @@ -388,17 +396,17 @@ class EventsQueryPersonColumn(BaseModel): class FilterLogicalOperator(str, Enum): - AND = "AND" - OR = "OR" + AND_ = "AND" + OR_ = "OR" class FunnelConversionWindowTimeUnit(str, Enum): - second = "second" - minute = "minute" - hour = "hour" - day = "day" - week = "week" - month = "month" + SECOND = "second" + MINUTE = "minute" + HOUR = "hour" + DAY = "day" + WEEK = "week" + MONTH = "month" class FunnelCorrelationResult(BaseModel): @@ -410,9 +418,9 @@ class FunnelCorrelationResult(BaseModel): class FunnelCorrelationResultsType(str, Enum): - events = "events" - properties = "properties" - event_with_properties = "event_with_properties" + EVENTS = "events" + PROPERTIES = "properties" + EVENT_WITH_PROPERTIES = "event_with_properties" class FunnelExclusionLegacy(BaseModel): @@ -438,19 +446,19 @@ class FunnelExclusionSteps(BaseModel): class FunnelLayout(str, Enum): - horizontal = "horizontal" - vertical = "vertical" + HORIZONTAL = "horizontal" + VERTICAL = "vertical" class FunnelPathType(str, Enum): - funnel_path_before_step = "funnel_path_before_step" - funnel_path_between_steps = "funnel_path_between_steps" - funnel_path_after_step = "funnel_path_after_step" + FUNNEL_PATH_BEFORE_STEP = "funnel_path_before_step" + FUNNEL_PATH_BETWEEN_STEPS = "funnel_path_between_steps" + FUNNEL_PATH_AFTER_STEP = "funnel_path_after_step" class FunnelStepReference(str, Enum): - total = "total" - previous = "previous" + TOTAL = "total" + PREVIOUS = "previous" class FunnelTimeToConvertResults(BaseModel): @@ -462,9 +470,9 @@ class FunnelTimeToConvertResults(BaseModel): class FunnelVizType(str, Enum): - steps = "steps" - time_to_convert = "time_to_convert" - trends = "trends" + STEPS = "steps" + TIME_TO_CONVERT = "time_to_convert" + TRENDS = "trends" class GoalLine(BaseModel): @@ -486,40 +494,40 @@ class HogQLNotice(BaseModel): class BounceRatePageViewMode(str, Enum): - count_pageviews = "count_pageviews" - uniq_urls = "uniq_urls" + COUNT_PAGEVIEWS = "count_pageviews" + UNIQ_URLS = "uniq_urls" class InCohortVia(str, Enum): - auto = "auto" - leftjoin = "leftjoin" - subquery = "subquery" - leftjoin_conjoined = "leftjoin_conjoined" + AUTO = "auto" + LEFTJOIN = "leftjoin" + SUBQUERY = "subquery" + LEFTJOIN_CONJOINED = "leftjoin_conjoined" class MaterializationMode(str, Enum): - auto = "auto" - legacy_null_as_string = "legacy_null_as_string" - legacy_null_as_null = "legacy_null_as_null" - disabled = "disabled" + AUTO = "auto" + LEGACY_NULL_AS_STRING = "legacy_null_as_string" + LEGACY_NULL_AS_NULL = "legacy_null_as_null" + DISABLED = "disabled" class PersonsArgMaxVersion(str, Enum): - auto = "auto" - v1 = "v1" - v2 = "v2" + AUTO = "auto" + V1 = "v1" + V2 = "v2" class PersonsJoinMode(str, Enum): - inner = "inner" - left = "left" + INNER = "inner" + LEFT = "left" class PersonsOnEventsMode(str, Enum): - disabled = "disabled" - person_id_no_override_properties_on_events = "person_id_no_override_properties_on_events" - person_id_override_properties_on_events = "person_id_override_properties_on_events" - person_id_override_properties_joined = "person_id_override_properties_joined" + DISABLED = "disabled" + PERSON_ID_NO_OVERRIDE_PROPERTIES_ON_EVENTS = "person_id_no_override_properties_on_events" + PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS = "person_id_override_properties_on_events" + PERSON_ID_OVERRIDE_PROPERTIES_JOINED = "person_id_override_properties_joined" class HogQLQueryModifiers(BaseModel): @@ -548,8 +556,8 @@ class HogQueryResponse(BaseModel): class Compare(str, Enum): - current = "current" - previous = "previous" + CURRENT = "current" + PREVIOUS = "previous" class DayItem(BaseModel): @@ -580,26 +588,29 @@ class InsightDateRange(BaseModel): date_to: Optional[str] = None explicitDate: Optional[bool] = Field( default=False, - description="Whether the date_from and date_to should be used verbatim. Disables rounding to the start and end of period.", + description=( + "Whether the date_from and date_to should be used verbatim. Disables rounding to the start and end of" + " period." + ), ) class InsightFilterProperty(str, Enum): - trendsFilter = "trendsFilter" - funnelsFilter = "funnelsFilter" - retentionFilter = "retentionFilter" - pathsFilter = "pathsFilter" - stickinessFilter = "stickinessFilter" - lifecycleFilter = "lifecycleFilter" + TRENDS_FILTER = "trendsFilter" + FUNNELS_FILTER = "funnelsFilter" + RETENTION_FILTER = "retentionFilter" + PATHS_FILTER = "pathsFilter" + STICKINESS_FILTER = "stickinessFilter" + LIFECYCLE_FILTER = "lifecycleFilter" class InsightNodeKind(str, Enum): - TrendsQuery = "TrendsQuery" - FunnelsQuery = "FunnelsQuery" - RetentionQuery = "RetentionQuery" - PathsQuery = "PathsQuery" - StickinessQuery = "StickinessQuery" - LifecycleQuery = "LifecycleQuery" + TRENDS_QUERY = "TrendsQuery" + FUNNELS_QUERY = "FunnelsQuery" + RETENTION_QUERY = "RetentionQuery" + PATHS_QUERY = "PathsQuery" + STICKINESS_QUERY = "StickinessQuery" + LIFECYCLE_QUERY = "LifecycleQuery" class InsightType(str, Enum): @@ -615,55 +626,55 @@ class InsightType(str, Enum): class IntervalType(str, Enum): - minute = "minute" - hour = "hour" - day = "day" - week = "week" - month = "month" + MINUTE = "minute" + HOUR = "hour" + DAY = "day" + WEEK = "week" + MONTH = "month" class LifecycleToggle(str, Enum): - new = "new" - resurrecting = "resurrecting" - returning = "returning" - dormant = "dormant" + NEW = "new" + RESURRECTING = "resurrecting" + RETURNING = "returning" + DORMANT = "dormant" class NodeKind(str, Enum): - EventsNode = "EventsNode" - ActionsNode = "ActionsNode" - DataWarehouseNode = "DataWarehouseNode" - EventsQuery = "EventsQuery" - PersonsNode = "PersonsNode" - HogQuery = "HogQuery" - HogQLQuery = "HogQLQuery" - HogQLMetadata = "HogQLMetadata" - HogQLAutocomplete = "HogQLAutocomplete" - ActorsQuery = "ActorsQuery" - FunnelsActorsQuery = "FunnelsActorsQuery" - FunnelCorrelationActorsQuery = "FunnelCorrelationActorsQuery" - SessionsTimelineQuery = "SessionsTimelineQuery" - DataTableNode = "DataTableNode" - DataVisualizationNode = "DataVisualizationNode" - SavedInsightNode = "SavedInsightNode" - InsightVizNode = "InsightVizNode" - TrendsQuery = "TrendsQuery" - FunnelsQuery = "FunnelsQuery" - RetentionQuery = "RetentionQuery" - PathsQuery = "PathsQuery" - StickinessQuery = "StickinessQuery" - LifecycleQuery = "LifecycleQuery" - InsightActorsQuery = "InsightActorsQuery" - InsightActorsQueryOptions = "InsightActorsQueryOptions" - FunnelCorrelationQuery = "FunnelCorrelationQuery" - WebOverviewQuery = "WebOverviewQuery" - WebTopClicksQuery = "WebTopClicksQuery" - WebStatsTableQuery = "WebStatsTableQuery" - TimeToSeeDataSessionsQuery = "TimeToSeeDataSessionsQuery" - TimeToSeeDataQuery = "TimeToSeeDataQuery" - TimeToSeeDataSessionsJSONNode = "TimeToSeeDataSessionsJSONNode" - TimeToSeeDataSessionsWaterfallNode = "TimeToSeeDataSessionsWaterfallNode" - DatabaseSchemaQuery = "DatabaseSchemaQuery" + EVENTS_NODE = "EventsNode" + ACTIONS_NODE = "ActionsNode" + DATA_WAREHOUSE_NODE = "DataWarehouseNode" + EVENTS_QUERY = "EventsQuery" + PERSONS_NODE = "PersonsNode" + HOG_QUERY = "HogQuery" + HOG_QL_QUERY = "HogQLQuery" + HOG_QL_METADATA = "HogQLMetadata" + HOG_QL_AUTOCOMPLETE = "HogQLAutocomplete" + ACTORS_QUERY = "ActorsQuery" + FUNNELS_ACTORS_QUERY = "FunnelsActorsQuery" + FUNNEL_CORRELATION_ACTORS_QUERY = "FunnelCorrelationActorsQuery" + SESSIONS_TIMELINE_QUERY = "SessionsTimelineQuery" + DATA_TABLE_NODE = "DataTableNode" + DATA_VISUALIZATION_NODE = "DataVisualizationNode" + SAVED_INSIGHT_NODE = "SavedInsightNode" + INSIGHT_VIZ_NODE = "InsightVizNode" + TRENDS_QUERY = "TrendsQuery" + FUNNELS_QUERY = "FunnelsQuery" + RETENTION_QUERY = "RetentionQuery" + PATHS_QUERY = "PathsQuery" + STICKINESS_QUERY = "StickinessQuery" + LIFECYCLE_QUERY = "LifecycleQuery" + INSIGHT_ACTORS_QUERY = "InsightActorsQuery" + INSIGHT_ACTORS_QUERY_OPTIONS = "InsightActorsQueryOptions" + FUNNEL_CORRELATION_QUERY = "FunnelCorrelationQuery" + WEB_OVERVIEW_QUERY = "WebOverviewQuery" + WEB_TOP_CLICKS_QUERY = "WebTopClicksQuery" + WEB_STATS_TABLE_QUERY = "WebStatsTableQuery" + TIME_TO_SEE_DATA_SESSIONS_QUERY = "TimeToSeeDataSessionsQuery" + TIME_TO_SEE_DATA_QUERY = "TimeToSeeDataQuery" + TIME_TO_SEE_DATA_SESSIONS_JSON_NODE = "TimeToSeeDataSessionsJSONNode" + TIME_TO_SEE_DATA_SESSIONS_WATERFALL_NODE = "TimeToSeeDataSessionsWaterfallNode" + DATABASE_SCHEMA_QUERY = "DatabaseSchemaQuery" class PathCleaningFilter(BaseModel): @@ -675,10 +686,10 @@ class PathCleaningFilter(BaseModel): class PathType(str, Enum): - field_pageview = "$pageview" - field_screen = "$screen" - custom_event = "custom_event" - hogql = "hogql" + FIELD_PAGEVIEW = "$pageview" + FIELD_SCREEN = "$screen" + CUSTOM_EVENT = "custom_event" + HOGQL = "hogql" class PathsFilter(BaseModel): @@ -724,51 +735,51 @@ class PathsFilterLegacy(BaseModel): class PropertyFilterType(str, Enum): - meta = "meta" - event = "event" - person = "person" - element = "element" - feature = "feature" - session = "session" - cohort = "cohort" - recording = "recording" - group = "group" - hogql = "hogql" - data_warehouse = "data_warehouse" - data_warehouse_person_property = "data_warehouse_person_property" + META = "meta" + EVENT = "event" + PERSON = "person" + ELEMENT = "element" + FEATURE = "feature" + SESSION = "session" + COHORT = "cohort" + RECORDING = "recording" + GROUP = "group" + HOGQL = "hogql" + DATA_WAREHOUSE = "data_warehouse" + DATA_WAREHOUSE_PERSON_PROPERTY = "data_warehouse_person_property" class PropertyMathType(str, Enum): - avg = "avg" - sum = "sum" - min = "min" - max = "max" - median = "median" - p90 = "p90" - p95 = "p95" - p99 = "p99" + AVG = "avg" + SUM = "sum" + MIN = "min" + MAX = "max" + MEDIAN = "median" + P90 = "p90" + P95 = "p95" + P99 = "p99" class PropertyOperator(str, Enum): - exact = "exact" - is_not = "is_not" - icontains = "icontains" - not_icontains = "not_icontains" - regex = "regex" - not_regex = "not_regex" - gt = "gt" - gte = "gte" - lt = "lt" - lte = "lte" - is_set = "is_set" - is_not_set = "is_not_set" - is_date_exact = "is_date_exact" - is_date_before = "is_date_before" - is_date_after = "is_date_after" - between = "between" - not_between = "not_between" - min = "min" - max = "max" + EXACT = "exact" + IS_NOT = "is_not" + ICONTAINS = "icontains" + NOT_ICONTAINS = "not_icontains" + REGEX = "regex" + NOT_REGEX = "not_regex" + GT = "gt" + GTE = "gte" + LT = "lt" + LTE = "lte" + IS_SET = "is_set" + IS_NOT_SET = "is_not_set" + IS_DATE_EXACT = "is_date_exact" + IS_DATE_BEFORE = "is_date_before" + IS_DATE_AFTER = "is_date_after" + BETWEEN = "between" + NOT_BETWEEN = "not_between" + MIN = "min" + MAX = "max" class QueryResponseAlternative1(BaseModel): @@ -864,8 +875,8 @@ class RecordingDurationFilter(BaseModel): class Kind1(str, Enum): - ActionsNode = "ActionsNode" - EventsNode = "EventsNode" + ACTIONS_NODE = "ActionsNode" + EVENTS_NODE = "EventsNode" class RetentionEntity(BaseModel): @@ -882,20 +893,20 @@ class RetentionEntity(BaseModel): class RetentionReference(str, Enum): - total = "total" - previous = "previous" + TOTAL = "total" + PREVIOUS = "previous" class RetentionPeriod(str, Enum): - Hour = "Hour" - Day = "Day" - Week = "Week" - Month = "Month" + HOUR = "Hour" + DAY = "Day" + WEEK = "Week" + MONTH = "Month" class RetentionType(str, Enum): - retention_recurring = "retention_recurring" - retention_first_time = "retention_first_time" + RETENTION_RECURRING = "retention_recurring" + RETENTION_FIRST_TIME = "retention_first_time" class RetentionValue(BaseModel): @@ -925,9 +936,9 @@ class SessionPropertyFilter(BaseModel): class StepOrderValue(str, Enum): - strict = "strict" - unordered = "unordered" - ordered = "ordered" + STRICT = "strict" + UNORDERED = "unordered" + ORDERED = "ordered" class StickinessFilter(BaseModel): @@ -1059,13 +1070,13 @@ class TrendsFilter(BaseModel): model_config = ConfigDict( extra="forbid", ) - aggregationAxisFormat: Optional[AggregationAxisFormat] = AggregationAxisFormat.numeric + aggregationAxisFormat: Optional[AggregationAxisFormat] = AggregationAxisFormat.NUMERIC aggregationAxisPostfix: Optional[str] = None aggregationAxisPrefix: Optional[str] = None breakdown_histogram_bin_count: Optional[float] = None compare: Optional[bool] = False decimalPlaces: Optional[float] = None - display: Optional[ChartDisplayType] = ChartDisplayType.ActionsLineGraph + display: Optional[ChartDisplayType] = ChartDisplayType.ACTIONS_LINE_GRAPH formula: Optional[str] = None hidden_legend_indexes: Optional[list[float]] = None showLabelsOnSeries: Optional[bool] = None @@ -1139,9 +1150,9 @@ class VizSpecificOptions(BaseModel): class Kind2(str, Enum): - unit = "unit" - duration_s = "duration_s" - percentage = "percentage" + UNIT = "unit" + DURATION_S = "duration_s" + PERCENTAGE = "percentage" class WebOverviewItem(BaseModel): @@ -1186,22 +1197,22 @@ class WebOverviewQueryResponse(BaseModel): class WebStatsBreakdown(str, Enum): - Page = "Page" - InitialPage = "InitialPage" - ExitPage = "ExitPage" - InitialChannelType = "InitialChannelType" - InitialReferringDomain = "InitialReferringDomain" - InitialUTMSource = "InitialUTMSource" - InitialUTMCampaign = "InitialUTMCampaign" - InitialUTMMedium = "InitialUTMMedium" - InitialUTMTerm = "InitialUTMTerm" - InitialUTMContent = "InitialUTMContent" - Browser = "Browser" + PAGE = "Page" + INITIAL_PAGE = "InitialPage" + EXIT_PAGE = "ExitPage" + INITIAL_CHANNEL_TYPE = "InitialChannelType" + INITIAL_REFERRING_DOMAIN = "InitialReferringDomain" + INITIAL_UTM_SOURCE = "InitialUTMSource" + INITIAL_UTM_CAMPAIGN = "InitialUTMCampaign" + INITIAL_UTM_MEDIUM = "InitialUTMMedium" + INITIAL_UTM_TERM = "InitialUTMTerm" + INITIAL_UTM_CONTENT = "InitialUTMContent" + BROWSER = "Browser" OS = "OS" - DeviceType = "DeviceType" - Country = "Country" - Region = "Region" - City = "City" + DEVICE_TYPE = "DeviceType" + COUNTRY = "Country" + REGION = "Region" + CITY = "City" class WebStatsTableQueryResponse(BaseModel): @@ -1292,7 +1303,7 @@ class BreakdownFilter(BaseModel): breakdown_histogram_bin_count: Optional[int] = None breakdown_limit: Optional[int] = None breakdown_normalize_url: Optional[bool] = None - breakdown_type: Optional[BreakdownType] = BreakdownType.event + breakdown_type: Optional[BreakdownType] = BreakdownType.EVENT breakdowns: Optional[list[Breakdown]] = None @@ -1859,7 +1870,7 @@ class EventPropertyFilter(BaseModel): ) key: str label: Optional[str] = None - operator: Optional[PropertyOperator] = PropertyOperator.exact + operator: Optional[PropertyOperator] = PropertyOperator.EXACT type: Literal["event"] = Field(default="event", description="Event properties") value: Optional[Union[str, float, list[Union[str, float]]]] = None @@ -2512,7 +2523,7 @@ class RetentionFilter(BaseModel): model_config = ConfigDict( extra="forbid", ) - period: Optional[RetentionPeriod] = RetentionPeriod.Day + period: Optional[RetentionPeriod] = RetentionPeriod.DAY retentionReference: Optional[RetentionReference] = None retentionType: Optional[RetentionType] = None returningEntity: Optional[RetentionEntity] = None @@ -3455,19 +3466,19 @@ class FunnelsFilter(BaseModel): extra="forbid", ) binCount: Optional[int] = None - breakdownAttributionType: Optional[BreakdownAttributionType] = BreakdownAttributionType.first_touch + breakdownAttributionType: Optional[BreakdownAttributionType] = BreakdownAttributionType.FIRST_TOUCH breakdownAttributionValue: Optional[int] = None exclusions: Optional[list[Union[FunnelExclusionEventsNode, FunnelExclusionActionsNode]]] = [] funnelAggregateByHogQL: Optional[str] = None funnelFromStep: Optional[int] = None - funnelOrderType: Optional[StepOrderValue] = StepOrderValue.ordered - funnelStepReference: Optional[FunnelStepReference] = FunnelStepReference.total + funnelOrderType: Optional[StepOrderValue] = StepOrderValue.ORDERED + funnelStepReference: Optional[FunnelStepReference] = FunnelStepReference.TOTAL funnelToStep: Optional[int] = None - funnelVizType: Optional[FunnelVizType] = FunnelVizType.steps + funnelVizType: Optional[FunnelVizType] = FunnelVizType.STEPS funnelWindowInterval: Optional[int] = 14 - funnelWindowIntervalUnit: Optional[FunnelConversionWindowTimeUnit] = FunnelConversionWindowTimeUnit.day + funnelWindowIntervalUnit: Optional[FunnelConversionWindowTimeUnit] = FunnelConversionWindowTimeUnit.DAY hidden_legend_breakdowns: Optional[list[str]] = None - layout: Optional[FunnelLayout] = FunnelLayout.vertical + layout: Optional[FunnelLayout] = FunnelLayout.VERTICAL class HasPropertiesNode(RootModel[Union[EventsNode, EventsQuery, PersonsNode]]): @@ -3551,7 +3562,7 @@ class StickinessQuery(BaseModel): default=False, description="Exclude internal and test users by applying the respective filters" ) interval: Optional[IntervalType] = Field( - default=IntervalType.day, + default=IntervalType.DAY, description="Granularity of the response. Can be one of `hour`, `day`, `week` or `month`", ) kind: Literal["StickinessQuery"] = "StickinessQuery" @@ -3600,7 +3611,7 @@ class TrendsQuery(BaseModel): default=False, description="Exclude internal and test users by applying the respective filters" ) interval: Optional[IntervalType] = Field( - default=IntervalType.day, + default=IntervalType.DAY, description="Granularity of the response. Can be one of `hour`, `day`, `week` or `month`", ) kind: Literal["TrendsQuery"] = "TrendsQuery" @@ -3658,7 +3669,10 @@ class FilterType(BaseModel): events: Optional[list[dict[str, Any]]] = None explicit_date: Optional[Union[bool, str]] = Field( default=None, - description='Whether the `date_from` and `date_to` should be used verbatim. Disables rounding to the start and end of period. Strings are cast to bools, e.g. "true" -> true.', + description=( + "Whether the `date_from` and `date_to` should be used verbatim. Disables rounding to the start and end of" + ' period. Strings are cast to bools, e.g. "true" -> true.' + ), ) filter_test_accounts: Optional[bool] = None from_dashboard: Optional[Union[bool, float]] = None @@ -3937,7 +3951,7 @@ class LifecycleQuery(BaseModel): default=False, description="Exclude internal and test users by applying the respective filters" ) interval: Optional[IntervalType] = Field( - default=IntervalType.day, + default=IntervalType.DAY, description="Granularity of the response. Can be one of `hour`, `day`, `week` or `month`", ) kind: Literal["LifecycleQuery"] = "LifecycleQuery" @@ -4075,15 +4089,23 @@ class FunnelsActorsQuery(BaseModel): ) funnelCustomSteps: Optional[list[int]] = Field( default=None, - description="Custom step numbers to get persons for. This overrides `funnelStep`. Primarily for correlation use.", + description=( + "Custom step numbers to get persons for. This overrides `funnelStep`. Primarily for correlation use." + ), ) funnelStep: Optional[int] = Field( default=None, - description="Index of the step for which we want to get the timestamp for, per person. Positive for converted persons, negative for dropped of persons.", + description=( + "Index of the step for which we want to get the timestamp for, per person. Positive for converted persons," + " negative for dropped of persons." + ), ) funnelStepBreakdown: Optional[Union[str, float, list[Union[str, float]]]] = Field( default=None, - description="The breakdown value for which to get persons for. This is an array for person and event properties, a string for groups and an integer for cohorts.", + description=( + "The breakdown value for which to get persons for. This is an array for person and event properties, a" + " string for groups and an integer for cohorts." + ), ) funnelTrendsDropOff: Optional[bool] = None funnelTrendsEntrancePeriodStart: Optional[str] = Field( @@ -4264,7 +4286,10 @@ class ActorsQuery(BaseModel): list[Union[PersonPropertyFilter, CohortPropertyFilter, HogQLPropertyFilter, EmptyPropertyFilter]] ] = Field( default=None, - description="Currently only person filters supported. No filters for querying groups. See `filter_conditions()` in actor_strategies.py.", + description=( + "Currently only person filters supported. No filters for querying groups. See `filter_conditions()` in" + " actor_strategies.py." + ), ) kind: Literal["ActorsQuery"] = "ActorsQuery" limit: Optional[int] = None @@ -4277,7 +4302,10 @@ class ActorsQuery(BaseModel): list[Union[PersonPropertyFilter, CohortPropertyFilter, HogQLPropertyFilter, EmptyPropertyFilter]] ] = Field( default=None, - description="Currently only person filters supported. No filters for querying groups. See `filter_conditions()` in actor_strategies.py.", + description=( + "Currently only person filters supported. No filters for querying groups. See `filter_conditions()` in" + " actor_strategies.py." + ), ) response: Optional[ActorsQueryResponse] = None search: Optional[str] = None @@ -4394,7 +4422,10 @@ class QueryRequest(BaseModel): async_: Optional[bool] = Field( default=None, alias="async", - description="(Experimental) Whether to run the query asynchronously. Defaults to False. If True, the `id` of the query can be used to check the status and to cancel it.", + description=( + "(Experimental) Whether to run the query asynchronously. Defaults to False. If True, the `id` of the query" + " can be used to check the status and to cancel it." + ), examples=[True], ) client_query_id: Optional[str] = Field( @@ -4432,7 +4463,12 @@ class QueryRequest(BaseModel): DatabaseSchemaQuery, ] = Field( ..., - description='Submit a JSON string representing a query for PostHog data analysis, for example a HogQL query.\n\nExample payload:\n\n```\n\n{"query": {"kind": "HogQLQuery", "query": "select * from events limit 100"}}\n\n```\n\nFor more details on HogQL queries, see the [PostHog HogQL documentation](/docs/hogql#api-access).', + description=( + "Submit a JSON string representing a query for PostHog data analysis, for example a HogQL query.\n\nExample" + ' payload:\n\n```\n\n{"query": {"kind": "HogQLQuery", "query": "select * from events limit' + ' 100"}}\n\n```\n\nFor more details on HogQL queries, see the [PostHog HogQL' + " documentation](/docs/hogql#api-access)." + ), discriminator="kind", ) refresh: Optional[Union[bool, str]] = None diff --git a/posthog/schema_helpers.py b/posthog/schema_helpers.py index c50075403ea1a8..d3e6cd427b68d3 100644 --- a/posthog/schema_helpers.py +++ b/posthog/schema_helpers.py @@ -76,7 +76,7 @@ def serialize_query(self, next_serializer): # use a canonical value for each display category if "display" in dumped[insightFilterKey]: canonical_display = grouped_chart_display_types(dumped[insightFilterKey]["display"]) - if canonical_display == ChartDisplayType.ActionsLineGraph: + if canonical_display == ChartDisplayType.ACTIONS_LINE_GRAPH: del dumped[insightFilterKey]["display"] # default value, remove else: dumped[insightFilterKey]["display"] = canonical_display @@ -125,15 +125,15 @@ def filter_key_for_query(node: InsightQueryNode) -> str: def grouped_chart_display_types(display: ChartDisplayType) -> ChartDisplayType | None: if display in [ - ChartDisplayType.ActionsLineGraph, - ChartDisplayType.ActionsBar, - ChartDisplayType.ActionsAreaGraph, + ChartDisplayType.ACTIONS_LINE_GRAPH, + ChartDisplayType.ACTIONS_BAR, + ChartDisplayType.ACTIONS_AREA_GRAPH, ]: # time series - return ChartDisplayType.ActionsLineGraph - elif display in [ChartDisplayType.ActionsLineGraphCumulative]: + return ChartDisplayType.ACTIONS_LINE_GRAPH + elif display in [ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE]: # cumulative time series - return ChartDisplayType.ActionsLineGraphCumulative + return ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE else: # total value - return ChartDisplayType.ActionsBarValue + return ChartDisplayType.ACTIONS_BAR_VALUE diff --git a/posthog/session_recordings/queries/session_recording_list_from_replay_summary.py b/posthog/session_recordings/queries/session_recording_list_from_replay_summary.py index 50de99273228ee..78b204d8723141 100644 --- a/posthog/session_recordings/queries/session_recording_list_from_replay_summary.py +++ b/posthog/session_recordings/queries/session_recording_list_from_replay_summary.py @@ -194,7 +194,7 @@ def _data_to_return(self, results: list[Any]) -> list[dict[str, Any]]: def get_query(self) -> tuple[str, dict[str, Any]]: # we don't support PoE V1 - hopefully that's ok - if self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: return "", {} prop_query, prop_params = self._get_prop_groups( @@ -299,7 +299,7 @@ def _determine_should_join_events(self): ) has_poe_filters = ( - self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events + self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS and len( [ pg @@ -311,7 +311,7 @@ def _determine_should_join_events(self): ) has_poe_person_filter = ( - self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events + self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS and self._filter.person_uuid ) @@ -367,9 +367,11 @@ def format_event_filter(self, entity: Entity, prepend: str, team_id: int) -> tup prepend=prepend, allow_denormalized_props=True, has_person_id_joined=True, - person_properties_mode=PersonPropertiesMode.DIRECT_ON_EVENTS_WITH_POE_V2 - if self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events - else PersonPropertiesMode.USING_PERSON_PROPERTIES_COLUMN, + person_properties_mode=( + PersonPropertiesMode.DIRECT_ON_EVENTS_WITH_POE_V2 + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS + else PersonPropertiesMode.USING_PERSON_PROPERTIES_COLUMN + ), hogql_context=self._filter.hogql_context, ) filter_sql += f" {filters}" @@ -416,7 +418,7 @@ def build_event_filters(self) -> SummaryEventFiltersSQL: -- select the unique events in this session to support filtering sessions by presence of an event groupUniqArray(event) as event_names,""" - if self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events: + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS: person_id_clause, person_id_params = self._get_person_id_clause condition_sql += person_id_clause params = {**params, **person_id_params} @@ -493,7 +495,7 @@ def get_query(self, select_event_ids: bool = False) -> tuple[str, dict[str, Any] g for g in self._filter.property_groups.flat if ( - self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events + self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS and g.type == "person" ) or ( @@ -508,9 +510,11 @@ def get_query(self, select_event_ids: bool = False) -> tuple[str, dict[str, Any] # it is likely this can be returned to the default of True in future # but would need careful monitoring allow_denormalized_props=settings.ALLOW_DENORMALIZED_PROPS_IN_LISTING, - person_properties_mode=PersonPropertiesMode.DIRECT_ON_EVENTS_WITH_POE_V2 - if self._person_on_events_mode == PersonsOnEventsMode.person_id_override_properties_on_events - else PersonPropertiesMode.USING_PERSON_PROPERTIES_COLUMN, + person_properties_mode=( + PersonPropertiesMode.DIRECT_ON_EVENTS_WITH_POE_V2 + if self._person_on_events_mode == PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS + else PersonPropertiesMode.USING_PERSON_PROPERTIES_COLUMN + ), ) ( diff --git a/posthog/test/test_schema_helpers.py b/posthog/test/test_schema_helpers.py index 012bf21d0c3d9d..a0ca3ec5d789d0 100644 --- a/posthog/test/test_schema_helpers.py +++ b/posthog/test/test_schema_helpers.py @@ -35,8 +35,8 @@ def test_serializes_to_differing_json_for_default_value(self): equal property filters can be distinguished. """ - q1 = EventPropertyFilter(key="abc", operator=PropertyOperator.gt) - q2 = PersonPropertyFilter(key="abc", operator=PropertyOperator.gt) + q1 = EventPropertyFilter(key="abc", operator=PropertyOperator.GT) + q2 = PersonPropertyFilter(key="abc", operator=PropertyOperator.GT) self.assertNotEqual(to_dict(q1), to_dict(q2)) self.assertIn("'type': 'event'", str(to_dict(q1))) @@ -50,7 +50,7 @@ def test_serializes_to_same_json_for_default_value(self): q1 = EventPropertyFilter(key="abc") q2 = EventPropertyFilter(key="abc", operator=None) - q3 = EventPropertyFilter(key="abc", operator=PropertyOperator.exact) + q3 = EventPropertyFilter(key="abc", operator=PropertyOperator.EXACT) self.assertEqual(to_dict(q1), to_dict(q2)) self.assertEqual(to_dict(q2), to_dict(q3)) @@ -149,29 +149,29 @@ def test_serializes_trends_filter(self, f1, f2, num_keys): (None, {}, 0), # general: ordering of keys ( - {"funnelVizType": FunnelVizType.time_to_convert, "funnelOrderType": StepOrderValue.strict}, - {"funnelOrderType": StepOrderValue.strict, "funnelVizType": FunnelVizType.time_to_convert}, + {"funnelVizType": FunnelVizType.TIME_TO_CONVERT, "funnelOrderType": StepOrderValue.STRICT}, + {"funnelOrderType": StepOrderValue.STRICT, "funnelVizType": FunnelVizType.TIME_TO_CONVERT}, 2, ), # binCount # ({}, {"binCount": 4}, 0), ( - {"binCount": 4, "funnelVizType": FunnelVizType.time_to_convert}, - {"binCount": 4, "funnelVizType": FunnelVizType.time_to_convert}, + {"binCount": 4, "funnelVizType": FunnelVizType.TIME_TO_CONVERT}, + {"binCount": 4, "funnelVizType": FunnelVizType.TIME_TO_CONVERT}, 2, ), # breakdownAttributionType - ({}, {"breakdownAttributionType": BreakdownAttributionType.first_touch}, 0), + ({}, {"breakdownAttributionType": BreakdownAttributionType.FIRST_TOUCH}, 0), ( - {"breakdownAttributionType": BreakdownAttributionType.last_touch}, - {"breakdownAttributionType": BreakdownAttributionType.last_touch}, + {"breakdownAttributionType": BreakdownAttributionType.LAST_TOUCH}, + {"breakdownAttributionType": BreakdownAttributionType.LAST_TOUCH}, 1, ), # breakdownAttributionValue # ({}, {"breakdownAttributionValue": 2}, 0), ( - {"breakdownAttributionType": BreakdownAttributionType.step, "breakdownAttributionValue": 2}, - {"breakdownAttributionType": BreakdownAttributionType.step, "breakdownAttributionValue": 2}, + {"breakdownAttributionType": BreakdownAttributionType.STEP, "breakdownAttributionValue": 2}, + {"breakdownAttributionType": BreakdownAttributionType.STEP, "breakdownAttributionValue": 2}, 2, ), # exclusions @@ -187,35 +187,35 @@ def test_serializes_trends_filter(self, f1, f2, num_keys): # funnelFromStep and funnelToStep ({"funnelFromStep": 1, "funnelToStep": 2}, {"funnelFromStep": 1, "funnelToStep": 2}, 2), # funnelOrderType - ({}, {"funnelOrderType": StepOrderValue.ordered}, 0), - ({"funnelOrderType": StepOrderValue.strict}, {"funnelOrderType": StepOrderValue.strict}, 1), + ({}, {"funnelOrderType": StepOrderValue.ORDERED}, 0), + ({"funnelOrderType": StepOrderValue.STRICT}, {"funnelOrderType": StepOrderValue.STRICT}, 1), # funnelStepReference - ({}, {"funnelStepReference": FunnelStepReference.total}, 0), + ({}, {"funnelStepReference": FunnelStepReference.TOTAL}, 0), ( - {"funnelStepReference": FunnelStepReference.previous}, - {"funnelStepReference": FunnelStepReference.previous}, + {"funnelStepReference": FunnelStepReference.PREVIOUS}, + {"funnelStepReference": FunnelStepReference.PREVIOUS}, 1, ), # funnelVizType - ({}, {"funnelVizType": FunnelVizType.steps}, 0), - ({"funnelVizType": FunnelVizType.trends}, {"funnelVizType": FunnelVizType.trends}, 1), + ({}, {"funnelVizType": FunnelVizType.STEPS}, 0), + ({"funnelVizType": FunnelVizType.TRENDS}, {"funnelVizType": FunnelVizType.TRENDS}, 1), # funnelWindowInterval ({}, {"funnelWindowInterval": 14}, 0), ({"funnelWindowInterval": 12}, {"funnelWindowInterval": 12}, 1), # funnelWindowIntervalUnit - ({}, {"funnelWindowIntervalUnit": FunnelConversionWindowTimeUnit.day}, 0), + ({}, {"funnelWindowIntervalUnit": FunnelConversionWindowTimeUnit.DAY}, 0), ( - {"funnelWindowIntervalUnit": FunnelConversionWindowTimeUnit.week}, - {"funnelWindowIntervalUnit": FunnelConversionWindowTimeUnit.week}, + {"funnelWindowIntervalUnit": FunnelConversionWindowTimeUnit.WEEK}, + {"funnelWindowIntervalUnit": FunnelConversionWindowTimeUnit.WEEK}, 1, ), # hidden_legend_breakdowns # ({}, {"hidden_legend_breakdowns": []}, 0), # layout - ({}, {"breakdownAttributionType": BreakdownAttributionType.first_touch}, 0), + ({}, {"breakdownAttributionType": BreakdownAttributionType.FIRST_TOUCH}, 0), ( - {"breakdownAttributionType": BreakdownAttributionType.last_touch}, - {"breakdownAttributionType": BreakdownAttributionType.last_touch}, + {"breakdownAttributionType": BreakdownAttributionType.LAST_TOUCH}, + {"breakdownAttributionType": BreakdownAttributionType.LAST_TOUCH}, 1, ), ] diff --git a/posthog/test/test_team.py b/posthog/test/test_team.py index 1268c476a2adf6..44705cac3ca2dd 100644 --- a/posthog/test/test_team.py +++ b/posthog/test/test_team.py @@ -139,7 +139,7 @@ def test_team_on_cloud_uses_feature_flag_to_determine_person_on_events(self, moc with override_instance_config("PERSON_ON_EVENTS_ENABLED", False): team = Team.objects.create_with_data(organization=self.organization) self.assertEqual( - team.person_on_events_mode, PersonsOnEventsMode.person_id_override_properties_on_events + team.person_on_events_mode, PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS ) # called more than once when evaluating hogql mock_feature_enabled.assert_called_with( @@ -162,7 +162,7 @@ def test_team_on_self_hosted_uses_instance_setting_to_determine_person_on_events with override_instance_config("PERSON_ON_EVENTS_V2_ENABLED", True): team = Team.objects.create_with_data(organization=self.organization) self.assertEqual( - team.person_on_events_mode, PersonsOnEventsMode.person_id_override_properties_on_events + team.person_on_events_mode, PersonsOnEventsMode.PERSON_ID_OVERRIDE_PROPERTIES_ON_EVENTS ) for args_list in mock_feature_enabled.call_args_list: # It is ok if we check other feature flags, just not `persons-on-events-v2-reads-enabled` @@ -170,7 +170,7 @@ def test_team_on_self_hosted_uses_instance_setting_to_determine_person_on_events with override_instance_config("PERSON_ON_EVENTS_V2_ENABLED", False): team = Team.objects.create_with_data(organization=self.organization) - self.assertEqual(team.person_on_events_mode, PersonsOnEventsMode.disabled) + self.assertEqual(team.person_on_events_mode, PersonsOnEventsMode.DISABLED) for args_list in mock_feature_enabled.call_args_list: # It is ok if we check other feature flags, just not `persons-on-events-v2-reads-enabled` assert args_list[0][0] != "persons-on-events-v2-reads-enabled" diff --git a/posthog/warehouse/api/table.py b/posthog/warehouse/api/table.py index e460175620fe8c..58d6296cb2ae3e 100644 --- a/posthog/warehouse/api/table.py +++ b/posthog/warehouse/api/table.py @@ -200,7 +200,7 @@ def update_schema(self, request: request.Request, *args: Any, **kwargs: Any) -> for key, value in updates.items(): try: - DatabaseSerializedFieldType[value] + DatabaseSerializedFieldType[value.upper()] except: return response.Response( status=status.HTTP_400_BAD_REQUEST, diff --git a/posthog/warehouse/models/table.py b/posthog/warehouse/models/table.py index f8893768c03b49..d52adb48b123b2 100644 --- a/posthog/warehouse/models/table.py +++ b/posthog/warehouse/models/table.py @@ -35,14 +35,14 @@ from .external_table_definitions import external_tables SERIALIZED_FIELD_TO_CLICKHOUSE_MAPPING: dict[DatabaseSerializedFieldType, str] = { - DatabaseSerializedFieldType.integer: "Int64", - DatabaseSerializedFieldType.float: "Float64", - DatabaseSerializedFieldType.string: "String", - DatabaseSerializedFieldType.datetime: "DateTime64", - DatabaseSerializedFieldType.date: "Date", - DatabaseSerializedFieldType.boolean: "Bool", - DatabaseSerializedFieldType.array: "Array", - DatabaseSerializedFieldType.json: "Map", + DatabaseSerializedFieldType.INTEGER: "Int64", + DatabaseSerializedFieldType.FLOAT: "Float64", + DatabaseSerializedFieldType.STRING: "String", + DatabaseSerializedFieldType.DATETIME: "DateTime64", + DatabaseSerializedFieldType.DATE: "Date", + DatabaseSerializedFieldType.BOOLEAN: "Bool", + DatabaseSerializedFieldType.ARRAY: "Array", + DatabaseSerializedFieldType.JSON: "Map", } CLICKHOUSE_HOGQL_MAPPING = { From b9a8969b8f11006cc892aa6277d644f77e5748bd Mon Sep 17 00:00:00 2001 From: Tom Owers Date: Mon, 10 Jun 2024 14:30:33 +0100 Subject: [PATCH 06/35] chore(data-warehouse): Use the new DLT source method for Stripe (#22760) * Use the new DLT source method * Remove non incremental fields * Fixed tests * fixed mypy * fixed mypy * Fixed tests: * fixed mypy --- mypy-baseline.txt | 45 +- .../pipelines/rest_source/__init__.py | 351 ++++++++++++++ .../pipelines/rest_source/config_setup.py | 455 ++++++++++++++++++ .../pipelines/rest_source/exceptions.py | 5 + .../pipelines/rest_source/typing.py | 254 ++++++++++ .../pipelines/rest_source/utils.py | 36 ++ .../data_imports/pipelines/stripe/__init__.py | 225 +++++++++ .../data_imports/pipelines/stripe/helpers.py | 178 ------- .../pipelines/test/test_pipeline.py | 15 +- .../workflow_activities/import_data.py | 24 +- .../external_data/test_external_data_job.py | 197 ++++---- .../models/external_table_definitions.py | 4 - 12 files changed, 1485 insertions(+), 304 deletions(-) create mode 100644 posthog/temporal/data_imports/pipelines/rest_source/__init__.py create mode 100644 posthog/temporal/data_imports/pipelines/rest_source/config_setup.py create mode 100644 posthog/temporal/data_imports/pipelines/rest_source/exceptions.py create mode 100644 posthog/temporal/data_imports/pipelines/rest_source/typing.py create mode 100644 posthog/temporal/data_imports/pipelines/rest_source/utils.py delete mode 100644 posthog/temporal/data_imports/pipelines/stripe/helpers.py diff --git a/mypy-baseline.txt b/mypy-baseline.txt index 6a12d0a8cd1373..d3e4f2d3fc6059 100644 --- a/mypy-baseline.txt +++ b/mypy-baseline.txt @@ -3,6 +3,45 @@ posthog/temporal/common/utils.py:0: note: This is likely because "from_activity" posthog/temporal/common/utils.py:0: error: Argument 2 to "__get__" of "classmethod" has incompatible type "type[HeartbeatType]"; expected "type[Never]" [arg-type] posthog/warehouse/models/ssh_tunnel.py:0: error: Incompatible types in assignment (expression has type "NoEncryption", variable has type "BestAvailableEncryption") [assignment] posthog/temporal/data_imports/pipelines/zendesk/talk_api.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Dict entry 2 has incompatible type "Literal['auto']": "None"; expected "Literal['json_response', 'header_link', 'auto', 'single_page', 'cursor', 'offset', 'page_number']": "type[BasePaginator]" [dict-item] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "AuthConfigBase") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Argument 1 to "get_auth_class" has incompatible type "Literal['bearer', 'api_key', 'http_basic'] | None"; expected "Literal['bearer', 'api_key', 'http_basic']" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Need type annotation for "dependency_graph" [var-annotated] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Incompatible types in assignment (expression has type "None", target has type "ResolvedParam") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Incompatible return value type (got "tuple[TopologicalSorter[Any], dict[str, EndpointResource], dict[str, ResolvedParam]]", expected "tuple[Any, dict[str, EndpointResource], dict[str, ResolvedParam | None]]") [return-value] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unsupported right operand type for in ("str | Endpoint | None") [operator] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Value of type variable "StrOrLiteralStr" of "parse" of "Formatter" cannot be "str | None" [type-var] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unsupported right operand type for in ("dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None") [operator] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unsupported right operand type for in ("dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None") [operator] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Value of type "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None" is not indexable [index] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Item "None" of "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None" has no attribute "pop" [union-attr] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Value of type "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None" is not indexable [index] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Item "None" of "str | None" has no attribute "format" [union-attr] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Argument 1 to "single_entity_path" has incompatible type "str | None"; expected "str" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Item "None" of "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None" has no attribute "items" [union-attr] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Incompatible types in assignment (expression has type "str | None", variable has type "str") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Incompatible types in assignment (expression has type "str | None", variable has type "str") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Statement is unreachable [unreachable] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unpacked dict entry 0 has incompatible type "dict[str, Any] | None"; expected "SupportsKeysAndGetItem[str, Any]" [dict-item] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unpacked dict entry 1 has incompatible type "dict[str, Any] | None"; expected "SupportsKeysAndGetItem[str, Any]" [dict-item] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unpacked dict entry 0 has incompatible type "dict[str, Any] | None"; expected "SupportsKeysAndGetItem[str, ResolveParamConfig | IncrementalParamConfig | Any]" [dict-item] +posthog/temporal/data_imports/pipelines/rest_source/config_setup.py:0: error: Unpacked dict entry 1 has incompatible type "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None"; expected "SupportsKeysAndGetItem[str, ResolveParamConfig | IncrementalParamConfig | Any]" [dict-item] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Not all union combinations were tried because there are too many unions [misc] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 2 to "source" has incompatible type "str | None"; expected "str" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 3 to "source" has incompatible type "str | None"; expected "str" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 4 to "source" has incompatible type "int | None"; expected "int" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 6 to "source" has incompatible type "Schema | None"; expected "Schema" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 7 to "source" has incompatible type "Literal['evolve', 'discard_value', 'freeze', 'discard_row'] | TSchemaContractDict | None"; expected "Literal['evolve', 'discard_value', 'freeze', 'discard_row'] | TSchemaContractDict" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 8 to "source" has incompatible type "type[BaseConfiguration] | None"; expected "type[BaseConfiguration]" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 1 to "build_resource_dependency_graph" has incompatible type "EndpointResourceBase | None"; expected "EndpointResourceBase" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Need type annotation for "resources" (hint: "resources: dict[, ] = ...") [var-annotated] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Incompatible types in assignment (expression has type "ResolvedParam | None", variable has type "ResolvedParam") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Incompatible types in assignment (expression has type "list[str] | None", variable has type "list[str]") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 1 to "setup_incremental_object" has incompatible type "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None"; expected "dict[str, Any]" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Statement is unreachable [unreachable] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument 1 to "exclude_keys" has incompatible type "dict[str, ResolveParamConfig | IncrementalParamConfig | Any] | None"; expected "Mapping[str, Any]" [arg-type] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Incompatible default for argument "incremental_param" (default has type "IncrementalParam | None", argument has type "IncrementalParam") [assignment] +posthog/temporal/data_imports/pipelines/rest_source/__init__.py:0: error: Argument "module" to "SourceInfo" has incompatible type Module | None; expected Module [arg-type] posthog/hogql/database/schema/numbers.py:0: error: Incompatible types in assignment (expression has type "dict[str, IntegerDatabaseField]", variable has type "dict[str, FieldOrTable]") [assignment] posthog/hogql/database/schema/numbers.py:0: note: "Dict" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance posthog/hogql/database/schema/numbers.py:0: note: Consider using "Mapping" instead, which is covariant in the value type @@ -700,7 +739,6 @@ posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: posthog/temporal/tests/batch_exports/test_snowflake_batch_export_workflow.py:0: error: List item 0 has incompatible type "tuple[str, str, int, int, int, int, str, int]"; expected "tuple[str, str, int, int, str, str, str, str]" [list-item] posthog/temporal/tests/batch_exports/test_s3_batch_export_workflow.py:0: error: "tuple[Any, ...]" has no attribute "last_uploaded_part_timestamp" [attr-defined] posthog/temporal/tests/batch_exports/test_s3_batch_export_workflow.py:0: error: "tuple[Any, ...]" has no attribute "upload_state" [attr-defined] -posthog/temporal/data_imports/pipelines/test/test_pipeline.py:0: error: Argument "run_id" to "PipelineInputs" has incompatible type "UUID"; expected "str" [arg-type] posthog/migrations/0237_remove_timezone_from_teams.py:0: error: Argument 2 to "RunPython" has incompatible type "Callable[[Migration, Any], None]"; expected "_CodeCallable | None" [arg-type] posthog/migrations/0228_fix_tile_layouts.py:0: error: Argument 2 to "RunPython" has incompatible type "Callable[[Migration, Any], None]"; expected "_CodeCallable | None" [arg-type] posthog/api/plugin_log_entry.py:0: error: Name "timezone.datetime" is not defined [name-defined] @@ -709,11 +747,6 @@ posthog/api/plugin_log_entry.py:0: error: Name "timezone.datetime" is not define posthog/api/plugin_log_entry.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "datetime" [attr-defined] posthog/api/action.py:0: error: Argument 1 to has incompatible type "*tuple[str, ...]"; expected "type[BaseRenderer]" [arg-type] posthog/temporal/tests/batch_exports/test_redshift_batch_export_workflow.py:0: error: Incompatible types in assignment (expression has type "str | int", variable has type "int") [assignment] -posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Argument "run_id" to "ImportDataActivityInputs" has incompatible type "UUID"; expected "str" [arg-type] -posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Argument "run_id" to "ImportDataActivityInputs" has incompatible type "UUID"; expected "str" [arg-type] -posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Argument "run_id" to "ImportDataActivityInputs" has incompatible type "UUID"; expected "str" [arg-type] -posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Argument "run_id" to "ImportDataActivityInputs" has incompatible type "UUID"; expected "str" [arg-type] -posthog/temporal/tests/external_data/test_external_data_job.py:0: error: Argument "run_id" to "ImportDataActivityInputs" has incompatible type "UUID"; expected "str" [arg-type] posthog/api/test/batch_exports/conftest.py:0: error: Argument "activities" to "ThreadedWorker" has incompatible type "list[function]"; expected "Sequence[Callable[..., Any]]" [arg-type] posthog/api/test/test_team.py:0: error: "HttpResponse" has no attribute "json" [attr-defined] posthog/api/test/test_team.py:0: error: "HttpResponse" has no attribute "json" [attr-defined] diff --git a/posthog/temporal/data_imports/pipelines/rest_source/__init__.py b/posthog/temporal/data_imports/pipelines/rest_source/__init__.py new file mode 100644 index 00000000000000..88b212e4644ddc --- /dev/null +++ b/posthog/temporal/data_imports/pipelines/rest_source/__init__.py @@ -0,0 +1,351 @@ +"""Generic API Source""" + +from typing import ( + Any, + Optional, + cast, +) +from collections.abc import Generator, Callable +import graphlib # type: ignore[import,unused-ignore] + +import dlt +from dlt.common.validation import validate_dict +from dlt.common import jsonpath +from dlt.common.schema.schema import Schema +from dlt.common.schema.typing import TSchemaContract +from dlt.common.configuration.specs import BaseConfiguration + +from dlt.extract.incremental import Incremental +from dlt.extract.source import DltResource, DltSource + +from dlt.sources.helpers.rest_client.client import RESTClient +from dlt.sources.helpers.rest_client.paginators import BasePaginator +from dlt.sources.helpers.rest_client.typing import HTTPMethodBasic +from .typing import ( + ClientConfig, + ResolvedParam, + Endpoint, + EndpointResource, + RESTAPIConfig, +) +from .config_setup import ( + IncrementalParam, + create_auth, + create_paginator, + build_resource_dependency_graph, + process_parent_data_item, + setup_incremental_object, + create_response_hooks, +) +from .utils import exclude_keys # noqa: F401 + + +def rest_api_source( + config: RESTAPIConfig, + name: Optional[str] = None, + section: Optional[str] = None, + max_table_nesting: Optional[int] = None, + root_key: bool = False, + schema: Optional[Schema] = None, + schema_contract: Optional[TSchemaContract] = None, + spec: Optional[type[BaseConfiguration]] = None, +) -> DltSource: + """Creates and configures a REST API source for data extraction. + + Args: + config (RESTAPIConfig): Configuration for the REST API source. + name (str, optional): Name of the source. + section (str, optional): Section of the configuration file. + max_table_nesting (int, optional): Maximum depth of nested table above which + the remaining nodes are loaded as structs or JSON. + root_key (bool, optional): Enables merging on all resources by propagating + root foreign key to child tables. This option is most useful if you + plan to change write disposition of a resource to disable/enable merge. + Defaults to False. + schema (Schema, optional): An explicit `Schema` instance to be associated + with the source. If not present, `dlt` creates a new `Schema` object + with provided `name`. If such `Schema` already exists in the same + folder as the module containing the decorated function, such schema + will be loaded from file. + schema_contract (TSchemaContract, optional): Schema contract settings + that will be applied to this resource. + spec (type[BaseConfiguration], optional): A specification of configuration + and secret values required by the source. + + Returns: + DltSource: A configured dlt source. + + Example: + pokemon_source = rest_api_source({ + "client": { + "base_url": "https://pokeapi.co/api/v2/", + "paginator": "json_response", + }, + "endpoints": { + "pokemon": { + "params": { + "limit": 100, # Default page size is 20 + }, + "resource": { + "primary_key": "id", + } + }, + }, + }) + """ + decorated = dlt.source( + rest_api_resources, + name, + section, + max_table_nesting, + root_key, + schema, + schema_contract, + spec, + ) + + return decorated(config) + + +def rest_api_resources(config: RESTAPIConfig) -> list[DltResource]: + """Creates a list of resources from a REST API configuration. + + Args: + config (RESTAPIConfig): Configuration for the REST API source. + + Returns: + list[DltResource]: List of dlt resources. + + Example: + github_source = rest_api_resources({ + "client": { + "base_url": "https://api.github.com/repos/dlt-hub/dlt/", + "auth": { + "token": dlt.secrets["token"], + }, + }, + "resource_defaults": { + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "params": { + "per_page": 100, + }, + }, + }, + "resources": [ + { + "name": "issues", + "endpoint": { + "path": "issues", + "params": { + "sort": "updated", + "direction": "desc", + "state": "open", + "since": { + "type": "incremental", + "cursor_path": "updated_at", + "initial_value": "2024-01-25T11:21:28Z", + }, + }, + }, + }, + { + "name": "issue_comments", + "endpoint": { + "path": "issues/{issue_number}/comments", + "params": { + "issue_number": { + "type": "resolve", + "resource": "issues", + "field": "number", + } + }, + }, + }, + ], + }) + """ + + validate_dict(RESTAPIConfig, config, path=".") + + client_config = config["client"] + resource_defaults = config.get("resource_defaults", {}) + resource_list = config["resources"] + + ( + dependency_graph, + endpoint_resource_map, + resolved_param_map, + ) = build_resource_dependency_graph( + resource_defaults, + resource_list, + ) + + resources = create_resources( + client_config, + dependency_graph, + endpoint_resource_map, + resolved_param_map, + ) + + return list(resources.values()) + + +def create_resources( + client_config: ClientConfig, + dependency_graph: graphlib.TopologicalSorter, + endpoint_resource_map: dict[str, EndpointResource], + resolved_param_map: dict[str, Optional[ResolvedParam]], +) -> dict[str, DltResource]: + resources = {} + + for resource_name in dependency_graph.static_order(): + resource_name = cast(str, resource_name) + endpoint_resource = endpoint_resource_map[resource_name] + endpoint_config = cast(Endpoint, endpoint_resource["endpoint"]) + request_params = endpoint_config.get("params", {}) + request_json = endpoint_config.get("json", None) + paginator = create_paginator(endpoint_config.get("paginator")) + + resolved_param: ResolvedParam = resolved_param_map[resource_name] + + include_from_parent: list[str] = endpoint_resource.get("include_from_parent", []) + if not resolved_param and include_from_parent: + raise ValueError( + f"Resource {resource_name} has include_from_parent but is not " "dependent on another resource" + ) + + ( + incremental_object, + incremental_param, + ) = setup_incremental_object(request_params, endpoint_config.get("incremental")) + + client = RESTClient( + base_url=client_config["base_url"], + headers=client_config.get("headers"), + auth=create_auth(client_config.get("auth")), + paginator=create_paginator(client_config.get("paginator")), + ) + + hooks = create_response_hooks(endpoint_config.get("response_actions")) + + resource_kwargs = exclude_keys(endpoint_resource, {"endpoint", "include_from_parent"}) + + if resolved_param is None: + + def paginate_resource( + method: HTTPMethodBasic, + path: str, + params: dict[str, Any], + json: Optional[dict[str, Any]], + paginator: Optional[BasePaginator], + data_selector: Optional[jsonpath.TJsonPath], + hooks: Optional[dict[str, Any]], + client: RESTClient = client, + incremental_object: Optional[Incremental[Any]] = incremental_object, + incremental_param: IncrementalParam = incremental_param, + ) -> Generator[Any, None, None]: + if incremental_object: + params[incremental_param.start] = incremental_object.last_value + if incremental_param.end: + params[incremental_param.end] = incremental_object.end_value + + yield from client.paginate( + method=method, + path=path, + params=params, + json=json, + paginator=paginator, + data_selector=data_selector, + hooks=hooks, + ) + + resources[resource_name] = dlt.resource( + paginate_resource, + **resource_kwargs, # TODO: implement typing.Unpack + )( + method=endpoint_config.get("method", "get"), + path=endpoint_config.get("path"), + params=request_params, + json=request_json, + paginator=paginator, + data_selector=endpoint_config.get("data_selector"), + hooks=hooks, + ) + + else: + predecessor = resources[resolved_param.resolve_config["resource"]] + + base_params = exclude_keys(request_params, {resolved_param.param_name}) + + def paginate_dependent_resource( + items: list[dict[str, Any]], + method: HTTPMethodBasic, + path: str, + params: dict[str, Any], + paginator: Optional[BasePaginator], + data_selector: Optional[jsonpath.TJsonPath], + hooks: Optional[dict[str, Any]], + client: RESTClient = client, + resolved_param: ResolvedParam = resolved_param, + include_from_parent: list[str] = include_from_parent, + incremental_object: Optional[Incremental[Any]] = incremental_object, + incremental_param: IncrementalParam = incremental_param, + ) -> Generator[Any, None, None]: + if incremental_object: + params[incremental_param.start] = incremental_object.last_value + if incremental_param.end: + params[incremental_param.end] = incremental_object.end_value + + for item in items: + formatted_path, parent_record = process_parent_data_item( + path, item, resolved_param, include_from_parent + ) + + for child_page in client.paginate( + method=method, + path=formatted_path, + params=params, + paginator=paginator, + data_selector=data_selector, + hooks=hooks, + ): + if parent_record: + for child_record in child_page: + child_record.update(parent_record) + yield child_page + + resources[resource_name] = dlt.resource( # type: ignore[call-overload] + paginate_dependent_resource, + data_from=predecessor, + **resource_kwargs, # TODO: implement typing.Unpack + )( + method=endpoint_config.get("method", "get"), + path=endpoint_config.get("path"), + params=base_params, + paginator=paginator, + data_selector=endpoint_config.get("data_selector"), + hooks=hooks, + ) + + return resources + + +# XXX: This is a workaround pass test_dlt_init.py +# since the source uses dlt.source as a function +def _register_source(source_func: Callable[..., DltSource]) -> None: + import inspect + from dlt.common.configuration import get_fun_spec + from dlt.common.source import _SOURCES, SourceInfo + + spec = get_fun_spec(source_func) + func_module = inspect.getmodule(source_func) + _SOURCES[source_func.__name__] = SourceInfo( + SPEC=spec, + f=source_func, + module=func_module, + ) + + +_register_source(rest_api_source) diff --git a/posthog/temporal/data_imports/pipelines/rest_source/config_setup.py b/posthog/temporal/data_imports/pipelines/rest_source/config_setup.py new file mode 100644 index 00000000000000..9eda391449d312 --- /dev/null +++ b/posthog/temporal/data_imports/pipelines/rest_source/config_setup.py @@ -0,0 +1,455 @@ +from copy import copy +from typing import ( + Any, + Optional, + cast, + NamedTuple, +) +from collections.abc import Callable +import graphlib # type: ignore[import,unused-ignore] +import string + +import dlt +from dlt.common import logger +from dlt.common.configuration import resolve_configuration +from dlt.common.schema.utils import merge_columns +from dlt.common.utils import update_dict_nested +from dlt.common import jsonpath + +from dlt.extract.incremental import Incremental +from dlt.extract.utils import ensure_table_schema_columns + +from dlt.sources.helpers.requests import Response +from dlt.sources.helpers.rest_client.paginators import ( + BasePaginator, + SinglePagePaginator, + HeaderLinkPaginator, + JSONResponsePaginator, + JSONResponseCursorPaginator, + OffsetPaginator, + PageNumberPaginator, +) +from dlt.sources.helpers.rest_client.detector import single_entity_path +from dlt.sources.helpers.rest_client.exceptions import IgnoreResponseException +from dlt.sources.helpers.rest_client.auth import ( + AuthConfigBase, + HttpBasicAuth, + BearerTokenAuth, + APIKeyAuth, +) + +from .typing import ( + EndpointResourceBase, + PaginatorType, + AuthType, + AuthConfig, + IncrementalArgs, + IncrementalConfig, + PaginatorConfig, + ResolvedParam, + ResponseAction, + Endpoint, + EndpointResource, +) +from .utils import exclude_keys + + +PAGINATOR_MAP: dict[PaginatorType, type[BasePaginator]] = { + "json_response": JSONResponsePaginator, + "header_link": HeaderLinkPaginator, + "auto": None, + "single_page": SinglePagePaginator, + "cursor": JSONResponseCursorPaginator, + "offset": OffsetPaginator, + "page_number": PageNumberPaginator, +} + +AUTH_MAP: dict[AuthType, type[AuthConfigBase]] = { + "bearer": BearerTokenAuth, + "api_key": APIKeyAuth, + "http_basic": HttpBasicAuth, +} + + +class IncrementalParam(NamedTuple): + start: str + end: Optional[str] + + +def get_paginator_class(paginator_type: PaginatorType) -> type[BasePaginator]: + try: + return PAGINATOR_MAP[paginator_type] + except KeyError: + available_options = ", ".join(PAGINATOR_MAP.keys()) + raise ValueError(f"Invalid paginator: {paginator_type}. " f"Available options: {available_options}") + + +def create_paginator( + paginator_config: Optional[PaginatorConfig], +) -> Optional[BasePaginator]: + if isinstance(paginator_config, BasePaginator): + return paginator_config + + if isinstance(paginator_config, str): + paginator_class = get_paginator_class(paginator_config) + try: + # `auto` has no associated class in `PAGINATOR_MAP` + return paginator_class() if paginator_class else None + except TypeError: + raise ValueError( + f"Paginator {paginator_config} requires arguments to create an instance. Use {paginator_class} instance instead." + ) + + if isinstance(paginator_config, dict): + paginator_type = paginator_config.get("type", "auto") + paginator_class = get_paginator_class(paginator_type) + return paginator_class(**exclude_keys(paginator_config, {"type"})) if paginator_class else None + + return None + + +def get_auth_class(auth_type: AuthType) -> type[AuthConfigBase]: + try: + return AUTH_MAP[auth_type] + except KeyError: + available_options = ", ".join(AUTH_MAP.keys()) + raise ValueError(f"Invalid paginator: {auth_type}. " f"Available options: {available_options}") + + +def create_auth(auth_config: Optional[AuthConfig]) -> Optional[AuthConfigBase]: + auth: AuthConfigBase = None + if isinstance(auth_config, AuthConfigBase): + auth = auth_config + + if isinstance(auth_config, str): + auth_class = get_auth_class(auth_config) + auth = auth_class() + + if isinstance(auth_config, dict): + auth_type = auth_config.get("type", "bearer") + auth_class = get_auth_class(auth_type) + auth = auth_class(**exclude_keys(auth_config, {"type"})) + + if auth: + # TODO: provide explicitly (non-default) values as explicit explicit_value=dict(auth) + # this will resolve auth which is a configuration using current section context + return resolve_configuration(auth) + + return None + + +def setup_incremental_object( + request_params: dict[str, Any], + incremental_config: Optional[IncrementalConfig] = None, +) -> tuple[Optional[Incremental[Any]], Optional[IncrementalParam]]: + for key, value in request_params.items(): + if isinstance(value, dlt.sources.incremental): + return value, IncrementalParam(start=key, end=None) + if isinstance(value, dict) and value.get("type") == "incremental": + config = exclude_keys(value, {"type"}) + # TODO: implement param type to bind incremental to + return ( + dlt.sources.incremental(**config), + IncrementalParam(start=key, end=None), + ) + if incremental_config: + config = exclude_keys(incremental_config, {"start_param", "end_param"}) + return ( + dlt.sources.incremental(**cast(IncrementalArgs, config)), + IncrementalParam( + start=incremental_config["start_param"], + end=incremental_config.get("end_param"), + ), + ) + + return None, None + + +def make_parent_key_name(resource_name: str, field_name: str) -> str: + return f"_{resource_name}_{field_name}" + + +def build_resource_dependency_graph( + resource_defaults: EndpointResourceBase, + resource_list: list[str | EndpointResource], +) -> tuple[Any, dict[str, EndpointResource], dict[str, Optional[ResolvedParam]]]: + dependency_graph = graphlib.TopologicalSorter() + endpoint_resource_map: dict[str, EndpointResource] = {} + resolved_param_map: dict[str, ResolvedParam] = {} + + # expand all resources and index them + for resource_kwargs in resource_list: + if isinstance(resource_kwargs, dict): + # clone resource here, otherwise it needs to be cloned in several other places + # note that this clones only dict structure, keeping all instances without deepcopy + resource_kwargs = update_dict_nested({}, resource_kwargs) # type: ignore[assignment] + + endpoint_resource = _make_endpoint_resource(resource_kwargs, resource_defaults) + assert isinstance(endpoint_resource["endpoint"], dict) + _setup_single_entity_endpoint(endpoint_resource["endpoint"]) + _bind_path_params(endpoint_resource) + + resource_name = endpoint_resource["name"] + assert isinstance(resource_name, str), f"Resource name must be a string, got {type(resource_name)}" + + if resource_name in endpoint_resource_map: + raise ValueError(f"Resource {resource_name} has already been defined") + endpoint_resource_map[resource_name] = endpoint_resource + + # create dependency graph + for resource_name, endpoint_resource in endpoint_resource_map.items(): + assert isinstance(endpoint_resource["endpoint"], dict) + # connect transformers to resources via resolved params + resolved_params = _find_resolved_params(endpoint_resource["endpoint"]) + if len(resolved_params) > 1: + raise ValueError(f"Multiple resolved params for resource {resource_name}: {resolved_params}") + elif len(resolved_params) == 1: + resolved_param = resolved_params[0] + predecessor = resolved_param.resolve_config["resource"] + if predecessor not in endpoint_resource_map: + raise ValueError( + f"A transformer resource {resource_name} refers to non existing parent resource {predecessor} on {resolved_param}" + ) + dependency_graph.add(resource_name, predecessor) + resolved_param_map[resource_name] = resolved_param + else: + dependency_graph.add(resource_name) + resolved_param_map[resource_name] = None + + return dependency_graph, endpoint_resource_map, resolved_param_map + + +def _make_endpoint_resource(resource: str | EndpointResource, default_config: EndpointResourceBase) -> EndpointResource: + """ + Creates an EndpointResource object based on the provided resource + definition and merges it with the default configuration. + + This function supports defining a resource in multiple formats: + - As a string: The string is interpreted as both the resource name + and its endpoint path. + - As a dictionary: The dictionary must include `name` and `endpoint` + keys. The `endpoint` can be a string representing the path, + or a dictionary for more complex configurations. If the `endpoint` + is missing the `path` key, the resource name is used as the `path`. + """ + if isinstance(resource, str): + resource = {"name": resource, "endpoint": {"path": resource}} + return _merge_resource_endpoints(default_config, resource) + + if "endpoint" in resource: + if isinstance(resource["endpoint"], str): + resource["endpoint"] = {"path": resource["endpoint"]} + else: + # endpoint is optional + resource["endpoint"] = {} + + if "path" not in resource["endpoint"]: + resource["endpoint"]["path"] = resource["name"] # type: ignore + + return _merge_resource_endpoints(default_config, resource) + + +def _bind_path_params(resource: EndpointResource) -> None: + """Binds params declared in path to params available in `params`. Pops the + bound params but. Params of type `resolve` and `incremental` are skipped + and bound later. + """ + path_params: dict[str, Any] = {} + assert isinstance(resource["endpoint"], dict) # type guard + resolve_params = [r.param_name for r in _find_resolved_params(resource["endpoint"])] + path = resource["endpoint"]["path"] + for format_ in string.Formatter().parse(path): + name = format_[1] + if name: + params = resource["endpoint"].get("params", {}) + if name not in params and name not in path_params: + raise ValueError( + f"The path {path} defined in resource {resource['name']} requires param with name {name} but it is not found in {params}" + ) + if name in resolve_params: + resolve_params.remove(name) + if name in params: + if not isinstance(params[name], dict): + # bind resolved param and pop it from endpoint + path_params[name] = params.pop(name) + else: + param_type = params[name].get("type") + if param_type != "resolve": + raise ValueError( + f"The path {path} defined in resource {resource['name']} tries to bind param {name} with type {param_type}. Paths can only bind 'resource' type params." + ) + # resolved params are bound later + path_params[name] = "{" + name + "}" + + if len(resolve_params) > 0: + raise NotImplementedError( + f"Resource {resource['name']} defines resolve params {resolve_params} that are not bound in path {path}. Resolve query params not supported yet." + ) + + resource["endpoint"]["path"] = path.format(**path_params) + + +def _setup_single_entity_endpoint(endpoint: Endpoint) -> Endpoint: + """Tries to guess if the endpoint refers to a single entity and when detected: + * if `data_selector` was not specified (or is None), "$" is selected + * if `paginator` was not specified (or is None), SinglePagePaginator is selected + + Endpoint is modified in place and returned + """ + # try to guess if list of entities or just single entity is returned + if single_entity_path(endpoint["path"]): + if endpoint.get("data_selector") is None: + endpoint["data_selector"] = "$" + if endpoint.get("paginator") is None: + endpoint["paginator"] = SinglePagePaginator() + return endpoint + + +def _find_resolved_params(endpoint_config: Endpoint) -> list[ResolvedParam]: + """ + Find all resolved params in the endpoint configuration and return + a list of ResolvedParam objects. + + Resolved params are of type ResolveParamConfig (bound param with a key "type" set to "resolve".) + """ + return [ + ResolvedParam(key, value) # type: ignore[arg-type] + for key, value in endpoint_config.get("params", {}).items() + if (isinstance(value, dict) and value.get("type") == "resolve") + ] + + +def _handle_response_actions(response: Response, actions: list[ResponseAction]) -> Optional[str]: + """Handle response actions based on the response and the provided actions.""" + content = response.text + + for action in actions: + status_code = action.get("status_code") + content_substr: str = action.get("content") + action_type: str = action.get("action") + + if status_code is not None and content_substr is not None: + if response.status_code == status_code and content_substr in content: + return action_type + + elif status_code is not None: + if response.status_code == status_code: + return action_type + + elif content_substr is not None: + if content_substr in content: + return action_type + + return None + + +def _create_response_actions_hook( + response_actions: list[ResponseAction], +) -> Callable[[Response, Any, Any], None]: + def response_actions_hook(response: Response, *args: Any, **kwargs: Any) -> None: + action_type = _handle_response_actions(response, response_actions) + if action_type == "ignore": + logger.info(f"Ignoring response with code {response.status_code} " f"and content '{response.json()}'.") + raise IgnoreResponseException + + # If no action has been taken and the status code indicates an error, + # raise an HTTP error based on the response status + if not action_type and response.status_code >= 400: + response.raise_for_status() + + return response_actions_hook + + +def create_response_hooks( + response_actions: Optional[list[ResponseAction]], +) -> Optional[dict[str, Any]]: + """Create response hooks based on the provided response actions. Note + that if the error status code is not handled by the response actions, + the default behavior is to raise an HTTP error. + + Example: + response_actions = [ + {"status_code": 404, "action": "ignore"}, + {"content": "Not found", "action": "ignore"}, + {"status_code": 429, "action": "retry"}, + {"status_code": 200, "content": "some text", "action": "retry"}, + ] + hooks = create_response_hooks(response_actions) + """ + if response_actions: + return {"response": [_create_response_actions_hook(response_actions)]} + return None + + +def process_parent_data_item( + path: str, + item: dict[str, Any], + resolved_param: ResolvedParam, + include_from_parent: list[str], +) -> tuple[str, dict[str, Any]]: + parent_resource_name = resolved_param.resolve_config["resource"] + + field_values = jsonpath.find_values(resolved_param.field_path, item) + + if not field_values: + field_path = resolved_param.resolve_config["field"] + raise ValueError( + f"Transformer expects a field '{field_path}' to be present in the incoming data from resource {parent_resource_name} in order to bind it to path param {resolved_param.param_name}. Available parent fields are {', '.join(item.keys())}" + ) + bound_path = path.format(**{resolved_param.param_name: field_values[0]}) + + parent_record: dict[str, Any] = {} + if include_from_parent: + for parent_key in include_from_parent: + child_key = make_parent_key_name(parent_resource_name, parent_key) + if parent_key not in item: + raise ValueError( + f"Transformer expects a field '{parent_key}' to be present in the incoming data from resource {parent_resource_name} in order to include it in child records under {child_key}. Available parent fields are {', '.join(item.keys())}" + ) + parent_record[child_key] = item[parent_key] + + return bound_path, parent_record + + +def _merge_resource_endpoints(default_config: EndpointResourceBase, config: EndpointResource) -> EndpointResource: + """Merges `default_config` and `config`, returns new instance of EndpointResource""" + # NOTE: config is normalized and always has "endpoint" field which is a dict + # TODO: could deep merge paginators and auths of the same type + + default_endpoint = default_config.get("endpoint", Endpoint()) + assert isinstance(default_endpoint, dict) + config_endpoint = config["endpoint"] + assert isinstance(config_endpoint, dict) + + merged_endpoint: Endpoint = { + **default_endpoint, + **{k: v for k, v in config_endpoint.items() if k not in ("json", "params")}, # type: ignore[typeddict-item] + } + # merge endpoint, only params and json are allowed to deep merge + if "json" in config_endpoint: + merged_endpoint["json"] = { + **(merged_endpoint.get("json", {})), + **config_endpoint["json"], + } + if "params" in config_endpoint: + merged_endpoint["params"] = { + **(merged_endpoint.get("json", {})), + **config_endpoint["params"], + } + # merge columns + if (default_columns := default_config.get("columns")) and (columns := config.get("columns")): + # merge only native dlt formats, skip pydantic and others + if isinstance(columns, list | dict) and isinstance(default_columns, list | dict): + # normalize columns + columns = ensure_table_schema_columns(columns) + default_columns = ensure_table_schema_columns(default_columns) + # merge columns with deep merging hints + config["columns"] = merge_columns(copy(default_columns), columns, merge_columns=True) + + # no need to deep merge resources + merged_resource: EndpointResource = { + **default_config, + **config, + "endpoint": merged_endpoint, + } + return merged_resource diff --git a/posthog/temporal/data_imports/pipelines/rest_source/exceptions.py b/posthog/temporal/data_imports/pipelines/rest_source/exceptions.py new file mode 100644 index 00000000000000..93e807d29b9fbc --- /dev/null +++ b/posthog/temporal/data_imports/pipelines/rest_source/exceptions.py @@ -0,0 +1,5 @@ +from dlt.common.exceptions import DltException + + +class RestApiException(DltException): + pass diff --git a/posthog/temporal/data_imports/pipelines/rest_source/typing.py b/posthog/temporal/data_imports/pipelines/rest_source/typing.py new file mode 100644 index 00000000000000..4a28912ccb2380 --- /dev/null +++ b/posthog/temporal/data_imports/pipelines/rest_source/typing.py @@ -0,0 +1,254 @@ +from typing import ( + Any, + Literal, + Optional, + TypedDict, +) +from dataclasses import dataclass, field + +from dlt.common import jsonpath +from dlt.common.typing import TSortOrder +from dlt.common.schema.typing import ( + TColumnNames, + TTableFormat, + TAnySchemaColumns, + TWriteDispositionConfig, + TSchemaContract, +) + +from dlt.extract.items import TTableHintTemplate +from dlt.extract.incremental.typing import LastValueFunc + +from dlt.sources.helpers.rest_client.paginators import BasePaginator +from dlt.sources.helpers.rest_client.typing import HTTPMethodBasic +from dlt.sources.helpers.rest_client.auth import AuthConfigBase, TApiKeyLocation + +from dlt.sources.helpers.rest_client.paginators import ( + SinglePagePaginator, + HeaderLinkPaginator, + JSONResponsePaginator, + JSONResponseCursorPaginator, + OffsetPaginator, + PageNumberPaginator, +) +from dlt.sources.helpers.rest_client.auth import ( + HttpBasicAuth, + BearerTokenAuth, + APIKeyAuth, +) + +PaginatorType = Literal[ + "json_response", + "header_link", + "auto", + "single_page", + "cursor", + "offset", + "page_number", +] + + +class PaginatorTypeConfig(TypedDict, total=True): + type: PaginatorType # noqa + + +class PageNumberPaginatorConfig(PaginatorTypeConfig, total=False): + """A paginator that uses page number-based pagination strategy.""" + + initial_page: Optional[int] + page_param: Optional[str] + total_path: Optional[jsonpath.TJsonPath] + maximum_page: Optional[int] + + +class OffsetPaginatorConfig(PaginatorTypeConfig, total=False): + """A paginator that uses offset-based pagination strategy.""" + + limit: int + offset: Optional[int] + offset_param: Optional[str] + limit_param: Optional[str] + total_path: Optional[jsonpath.TJsonPath] + maximum_offset: Optional[int] + + +class HeaderLinkPaginatorConfig(PaginatorTypeConfig, total=False): + """A paginator that uses the 'Link' header in HTTP responses + for pagination.""" + + links_next_key: Optional[str] + + +class JSONResponsePaginatorConfig(PaginatorTypeConfig, total=False): + """Locates the next page URL within the JSON response body. The key + containing the URL can be specified using a JSON path.""" + + next_url_path: Optional[jsonpath.TJsonPath] + + +class JSONResponseCursorPaginatorConfig(PaginatorTypeConfig, total=False): + """Uses a cursor parameter for pagination, with the cursor value found in + the JSON response body.""" + + cursor_path: Optional[jsonpath.TJsonPath] + cursor_param: Optional[str] + + +PaginatorConfig = ( + PaginatorType + | PageNumberPaginatorConfig + | OffsetPaginatorConfig + | HeaderLinkPaginatorConfig + | JSONResponsePaginatorConfig + | JSONResponseCursorPaginatorConfig + | BasePaginator + | SinglePagePaginator + | HeaderLinkPaginator + | JSONResponsePaginator + | JSONResponseCursorPaginator + | OffsetPaginator + | PageNumberPaginator +) + + +AuthType = Literal["bearer", "api_key", "http_basic"] + + +class AuthTypeConfig(TypedDict, total=True): + type: AuthType # noqa + + +class BearerTokenAuthConfig(TypedDict, total=False): + """Uses `token` for Bearer authentication in "Authorization" header.""" + + # we allow for a shorthand form of bearer auth, without a type + type: Optional[AuthType] # noqa + token: str + + +class ApiKeyAuthConfig(AuthTypeConfig, total=False): + """Uses provided `api_key` to create authorization data in the specified `location` (query, param, header, cookie) under specified `name`""" + + name: Optional[str] + api_key: str + location: Optional[TApiKeyLocation] + + +class HttpBasicAuthConfig(AuthTypeConfig, total=True): + """Uses HTTP basic authentication""" + + username: str + password: str + + +# TODO: add later +# class OAuthJWTAuthConfig(AuthTypeConfig, total=True): + + +AuthConfig = ( + AuthConfigBase + | AuthType + | BearerTokenAuthConfig + | ApiKeyAuthConfig + | HttpBasicAuthConfig + | BearerTokenAuth + | APIKeyAuth + | HttpBasicAuth +) + + +class ClientConfig(TypedDict, total=False): + base_url: str + headers: Optional[dict[str, str]] + auth: Optional[AuthConfig] + paginator: Optional[PaginatorConfig] + + +class IncrementalArgs(TypedDict, total=False): + cursor_path: str + initial_value: Optional[str] + last_value_func: Optional[LastValueFunc[str]] + primary_key: Optional[TTableHintTemplate[TColumnNames]] + end_value: Optional[str] + row_order: Optional[TSortOrder] + + +class IncrementalConfig(IncrementalArgs, total=False): + start_param: str + end_param: Optional[str] + + +ParamBindType = Literal["resolve", "incremental"] + + +class ParamBindConfig(TypedDict): + type: ParamBindType # noqa + + +class ResolveParamConfig(ParamBindConfig): + resource: str + field: str + + +class IncrementalParamConfig(ParamBindConfig, IncrementalArgs): + pass + # TODO: implement param type to bind incremental to + # param_type: Optional[Literal["start_param", "end_param"]] + + +@dataclass +class ResolvedParam: + param_name: str + resolve_config: ResolveParamConfig + field_path: jsonpath.TJsonPath = field(init=False) + + def __post_init__(self) -> None: + self.field_path = jsonpath.compile_path(self.resolve_config["field"]) + + +class ResponseAction(TypedDict, total=False): + status_code: Optional[int | str] + content: Optional[str] + action: str + + +class Endpoint(TypedDict, total=False): + path: Optional[str] + method: Optional[HTTPMethodBasic] + params: Optional[dict[str, ResolveParamConfig | IncrementalParamConfig | Any]] + json: Optional[dict[str, Any]] + paginator: Optional[PaginatorConfig] + data_selector: Optional[jsonpath.TJsonPath] + response_actions: Optional[list[ResponseAction]] + incremental: Optional[IncrementalConfig] + + +class ResourceBase(TypedDict, total=False): + """Defines hints that may be passed to `dlt.resource` decorator""" + + table_name: Optional[TTableHintTemplate[str]] + max_table_nesting: Optional[int] + write_disposition: Optional[TTableHintTemplate[TWriteDispositionConfig]] + parent: Optional[TTableHintTemplate[str]] + columns: Optional[TTableHintTemplate[TAnySchemaColumns]] + primary_key: Optional[TTableHintTemplate[TColumnNames]] + merge_key: Optional[TTableHintTemplate[TColumnNames]] + schema_contract: Optional[TTableHintTemplate[TSchemaContract]] + table_format: Optional[TTableHintTemplate[TTableFormat]] + selected: Optional[bool] + parallelized: Optional[bool] + + +class EndpointResourceBase(ResourceBase, total=False): + endpoint: Optional[str | Endpoint] + include_from_parent: Optional[list[str]] + + +class EndpointResource(EndpointResourceBase, total=False): + name: TTableHintTemplate[str] + + +class RESTAPIConfig(TypedDict): + client: ClientConfig + resource_defaults: Optional[EndpointResourceBase] + resources: list[str | EndpointResource] diff --git a/posthog/temporal/data_imports/pipelines/rest_source/utils.py b/posthog/temporal/data_imports/pipelines/rest_source/utils.py new file mode 100644 index 00000000000000..91eca3cf480049 --- /dev/null +++ b/posthog/temporal/data_imports/pipelines/rest_source/utils.py @@ -0,0 +1,36 @@ +from typing import Any +from collections.abc import Mapping, Iterable + +from dlt.common import logger +from dlt.extract.source import DltSource + + +def join_url(base_url: str, path: str) -> str: + if not base_url.endswith("/"): + base_url += "/" + return base_url + path.lstrip("/") + + +def exclude_keys(d: Mapping[str, Any], keys: Iterable[str]) -> dict[str, Any]: + """Removes specified keys from a dictionary and returns a new dictionary. + + Args: + d (Mapping[str, Any]): The dictionary to remove keys from. + keys (Iterable[str]): The keys to remove. + + Returns: + Dict[str, Any]: A new dictionary with the specified keys removed. + """ + return {k: v for k, v in d.items() if k not in keys} + + +def check_connection( + source: DltSource, + *resource_names: str, +) -> tuple[bool, str]: + try: + list(source.with_resources(*resource_names).add_limit(1)) + return (True, "") + except Exception as e: + logger.error(f"Error checking connection: {e}") + return (False, str(e)) diff --git a/posthog/temporal/data_imports/pipelines/stripe/__init__.py b/posthog/temporal/data_imports/pipelines/stripe/__init__.py index e69de29bb2d1d6..2fe2df90ce0e18 100644 --- a/posthog/temporal/data_imports/pipelines/stripe/__init__.py +++ b/posthog/temporal/data_imports/pipelines/stripe/__init__.py @@ -0,0 +1,225 @@ +import dlt +from dlt.sources.helpers.rest_client.paginators import BasePaginator +from dlt.sources.helpers.requests import Response, Request +from posthog.temporal.data_imports.pipelines.rest_source import RESTAPIConfig, rest_api_resources +from posthog.temporal.data_imports.pipelines.rest_source.typing import EndpointResource + + +def get_resource(name: str, is_incremental: bool) -> EndpointResource: + resources: dict[str, EndpointResource] = { + "BalanceTransaction": { + "name": "BalanceTransaction", + "table_name": "balance_transaction", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/balance_transactions", + "params": { + # the parameters below can optionally be configured + # "created": "OPTIONAL_CONFIG", + # "currency": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + "limit": 100, + # "payout": "OPTIONAL_CONFIG", + # "source": "OPTIONAL_CONFIG", + # "starting_after": "OPTIONAL_CONFIG", + # "type": "OPTIONAL_CONFIG", + }, + }, + }, + "Charge": { + "name": "Charge", + "table_name": "charge", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/charges", + "params": { + # the parameters below can optionally be configured + # "created": "OPTIONAL_CONFIG", + # "customer": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + "limit": 100, + # "payment_intent": "OPTIONAL_CONFIG", + # "starting_after": "OPTIONAL_CONFIG", + # "transfer_group": "OPTIONAL_CONFIG", + }, + }, + }, + "Customer": { + "name": "Customer", + "table_name": "customer", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/customers", + "params": { + # the parameters below can optionally be configured + # "created": "OPTIONAL_CONFIG", + # "email": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + "limit": 100, + # "starting_after": "OPTIONAL_CONFIG", + # "test_clock": "OPTIONAL_CONFIG", + }, + }, + }, + "Invoice": { + "name": "Invoice", + "table_name": "invoice", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/invoices", + "params": { + # the parameters below can optionally be configured + # "collection_method": "OPTIONAL_CONFIG", + "created[gte]": { + "type": "incremental", + "cursor_path": "created", + "initial_value": 0, # type: ignore + } + if is_incremental + else None, + # "customer": "OPTIONAL_CONFIG", + # "due_date": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + "limit": 100, + # "starting_after": "OPTIONAL_CONFIG", + # "status": "OPTIONAL_CONFIG", + # "subscription": "OPTIONAL_CONFIG", + }, + }, + }, + "Price": { + "name": "Price", + "table_name": "price", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/prices", + "params": { + # the parameters below can optionally be configured + # "active": "OPTIONAL_CONFIG", + # "created": "OPTIONAL_CONFIG", + # "currency": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + "limit": 100, + # "lookup_keys": "OPTIONAL_CONFIG", + # "product": "OPTIONAL_CONFIG", + # "recurring": "OPTIONAL_CONFIG", + # "starting_after": "OPTIONAL_CONFIG", + # "type": "OPTIONAL_CONFIG", + }, + }, + }, + "Product": { + "name": "Product", + "table_name": "product", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/products", + "params": { + # the parameters below can optionally be configured + # "active": "OPTIONAL_CONFIG", + # "created": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + # "ids": "OPTIONAL_CONFIG", + "limit": 100, + # "shippable": "OPTIONAL_CONFIG", + # "starting_after": "OPTIONAL_CONFIG", + # "url": "OPTIONAL_CONFIG", + }, + }, + }, + "Subscription": { + "name": "Subscription", + "table_name": "subscription", + "primary_key": "id", + "write_disposition": "merge", + "endpoint": { + "data_selector": "data", + "path": "/v1/subscriptions", + "params": { + # the parameters below can optionally be configured + # "collection_method": "OPTIONAL_CONFIG", + # "created": "OPTIONAL_CONFIG", + # "current_period_end": "OPTIONAL_CONFIG", + # "current_period_start": "OPTIONAL_CONFIG", + # "customer": "OPTIONAL_CONFIG", + # "ending_before": "OPTIONAL_CONFIG", + # "expand": "OPTIONAL_CONFIG", + "limit": 100, + # "price": "OPTIONAL_CONFIG", + # "starting_after": "OPTIONAL_CONFIG", + # "status": "OPTIONAL_CONFIG", + # "test_clock": "OPTIONAL_CONFIG", + }, + }, + }, + } + + return resources[name] + + +class StripePaginator(BasePaginator): + def update_state(self, response: Response) -> None: + res = response.json() + + self._starting_after = None + + if not res: + self._has_next_page = False + return + + if res["has_more"]: + self._has_next_page = True + + earliest_value_in_response = res["data"][-1]["id"] + self._starting_after = earliest_value_in_response + else: + self._has_next_page = False + + def update_request(self, request: Request) -> None: + if request.params is None: + request.params = {} + + request.params["starting_after"] = self._starting_after + + +@dlt.source +def stripe_source(api_key: str, account_id: str, endpoint: str, is_incremental: bool = False): + config: RESTAPIConfig = { + "client": { + "base_url": "https://api.stripe.com/", + "auth": { + "type": "http_basic", + "username": api_key, + "password": "", + }, + "headers": { + "Stripe-Account": account_id, + }, + "paginator": StripePaginator(), + }, + "resource_defaults": { + "primary_key": "id", + "write_disposition": "merge", + }, + "resources": [get_resource(endpoint, is_incremental)], + } + + yield from rest_api_resources(config) diff --git a/posthog/temporal/data_imports/pipelines/stripe/helpers.py b/posthog/temporal/data_imports/pipelines/stripe/helpers.py deleted file mode 100644 index 2dfad33b4a5a09..00000000000000 --- a/posthog/temporal/data_imports/pipelines/stripe/helpers.py +++ /dev/null @@ -1,178 +0,0 @@ -"""Stripe analytics source helpers""" - -from typing import Any, Optional, Union -from collections.abc import Iterable - -import stripe -import dlt -from dlt.common import pendulum -from dlt.sources import DltResource -from pendulum import DateTime -from asgiref.sync import sync_to_async -from posthog.temporal.common.logger import bind_temporal_worker_logger -from posthog.temporal.data_imports.pipelines.helpers import check_limit -from posthog.temporal.data_imports.pipelines.stripe.settings import INCREMENTAL_ENDPOINTS -from posthog.warehouse.models import ExternalDataJob - -from posthog.warehouse.models.external_table_definitions import get_dlt_mapping_for_external_table - -stripe.api_version = "2022-11-15" - - -def transform_date(date: Union[str, DateTime, int]) -> int: - if isinstance(date, str): - date = pendulum.from_format(date, "%Y-%m-%dT%H:%M:%SZ") - if isinstance(date, DateTime): - # convert to unix timestamp - date = int(date.timestamp()) - return date - - -async def stripe_get_data( - api_key: str, - account_id: str, - resource: str, - start_date: Optional[Any] = None, - end_date: Optional[Any] = None, - **kwargs: Any, -) -> dict[Any, Any]: - if start_date: - start_date = transform_date(start_date) - if end_date: - end_date = transform_date(end_date) - - if resource == "Subscription": - kwargs.update({"status": "all"}) - - _resource = getattr(stripe, resource) - - resource_dict = await sync_to_async(_resource.list)( - api_key=api_key, - stripe_account=account_id, - created={"gte": start_date, "lt": end_date}, - limit=100, - **kwargs, - ) - response = dict(resource_dict) - - return response - - -async def stripe_pagination( - api_key: str, - account_id: str, - endpoint: str, - team_id: int, - job_id: str, - schema_id: str, - starting_after: Optional[Any] = None, - start_date: Optional[Any] = None, - end_date: Optional[Any] = None, -): - """ - Retrieves data from an endpoint with pagination. - - Args: - endpoint (str): The endpoint to retrieve data from. - start_date (Optional[Any]): An optional start date to limit the data retrieved. Defaults to None. - end_date (Optional[Any]): An optional end date to limit the data retrieved. Defaults to None. - - Returns: - Iterable[TDataItem]: Data items retrieved from the endpoint. - """ - - logger = await bind_temporal_worker_logger(team_id) - logger.info(f"Stripe: getting {endpoint}") - - if endpoint in INCREMENTAL_ENDPOINTS: - _cursor_state = dlt.current.resource_state(f"team_{team_id}_{schema_id}_{endpoint}").setdefault( - "cursors", {"ending_before": None, "starting_after": None} - ) - _starting_after = _cursor_state.get("starting_after", None) - _ending_before = _cursor_state.get("ending_before", None) if _starting_after is None else None - else: - _starting_after = starting_after - _ending_before = None - - while True: - if _ending_before is not None: - logger.info(f"Stripe: getting {endpoint} before {_ending_before}") - elif _starting_after is not None: - logger.info(f"Stripe: getting {endpoint} after {_starting_after}") - - count = 0 - - response = await stripe_get_data( - api_key, - account_id, - endpoint, - ending_before=_ending_before, - starting_after=_starting_after, - start_date=start_date, - end_date=end_date, - ) - - if len(response["data"]) > 0: - latest_value_in_response = response["data"][0]["id"] - earliest_value_in_response = response["data"][-1]["id"] - - if endpoint in INCREMENTAL_ENDPOINTS: - # First pass, store the latest value - if _starting_after is None and _ending_before is None: - _cursor_state["ending_before"] = latest_value_in_response - - # currently scrolling from past to present - if _ending_before is not None: - _cursor_state["ending_before"] = latest_value_in_response - _ending_before = latest_value_in_response - # otherwise scrolling from present to past - else: - _starting_after = earliest_value_in_response - _cursor_state["starting_after"] = earliest_value_in_response - else: - _starting_after = earliest_value_in_response - else: - if endpoint in INCREMENTAL_ENDPOINTS: - _cursor_state["starting_after"] = None - - yield response["data"] - - count, status = await check_limit( - team_id=team_id, - job_id=job_id, - new_count=count + len(response["data"]), - ) - - if not response["has_more"] or status == ExternalDataJob.Status.CANCELLED: - break - - -@dlt.source(max_table_nesting=0) -def stripe_source( - api_key: str, - account_id: str, - endpoints: tuple[str, ...], - team_id, - job_id, - schema_id, - starting_after: Optional[str] = None, - start_date: Optional[Any] = None, - end_date: Optional[Any] = None, -) -> Iterable[DltResource]: - for endpoint in endpoints: - yield dlt.resource( - stripe_pagination, - name=endpoint, - write_disposition="append", - columns=get_dlt_mapping_for_external_table(f"stripe_{endpoint}".lower()), - )( - api_key=api_key, - account_id=account_id, - endpoint=endpoint, - team_id=team_id, - job_id=job_id, - schema_id=schema_id, - starting_after=starting_after, - start_date=start_date, - end_date=end_date, - ) diff --git a/posthog/temporal/data_imports/pipelines/test/test_pipeline.py b/posthog/temporal/data_imports/pipelines/test/test_pipeline.py index 071b818b01d1c4..23fc37c6d80f8a 100644 --- a/posthog/temporal/data_imports/pipelines/test/test_pipeline.py +++ b/posthog/temporal/data_imports/pipelines/test/test_pipeline.py @@ -6,7 +6,7 @@ import structlog from asgiref.sync import sync_to_async from posthog.temporal.data_imports.pipelines.pipeline import DataImportPipeline, PipelineInputs -from posthog.temporal.data_imports.pipelines.stripe.helpers import stripe_source +from posthog.temporal.data_imports.pipelines.stripe import stripe_source from posthog.test.base import APIBaseTest from posthog.warehouse.models.external_data_job import ExternalDataJob from posthog.warehouse.models.external_data_schema import ExternalDataSchema @@ -43,22 +43,13 @@ async def _create_pipeline(self, schema_name: str, incremental: bool): pipeline = DataImportPipeline( inputs=PipelineInputs( source_id=source.pk, - run_id=job.pk, + run_id=str(job.pk), schema_id=schema.pk, dataset_name=job.folder_path, job_type="Stripe", team_id=self.team.pk, ), - source=stripe_source( - api_key="", - account_id="", - endpoints=(schema_name,), - team_id=self.team.pk, - job_id=job.pk, - schema_id=schema.pk, - start_date=None, - end_date=None, - ), + source=stripe_source(api_key="", account_id="", endpoint=schema_name, is_incremental=False), logger=structlog.get_logger(), incremental=incremental, ) diff --git a/posthog/temporal/data_imports/workflow_activities/import_data.py b/posthog/temporal/data_imports/workflow_activities/import_data.py index 27b3866db49c8b..9062d389415ed9 100644 --- a/posthog/temporal/data_imports/workflow_activities/import_data.py +++ b/posthog/temporal/data_imports/workflow_activities/import_data.py @@ -1,5 +1,4 @@ import dataclasses -import datetime as dt from typing import Any import uuid @@ -11,7 +10,6 @@ from posthog.temporal.data_imports.pipelines.zendesk.credentials import ZendeskCredentialsToken from posthog.temporal.data_imports.pipelines.pipeline import DataImportPipeline, PipelineInputs -from posthog.utils import get_instance_region from posthog.warehouse.models import ( ExternalDataJob, ExternalDataSource, @@ -19,7 +17,6 @@ ) from posthog.temporal.common.logger import bind_temporal_worker_logger import asyncio -from django.utils import timezone from structlog.typing import FilteringBoundLogger from posthog.warehouse.models.external_data_schema import ExternalDataSchema, aget_schema_by_id from posthog.warehouse.models.ssh_tunnel import SSHTunnel @@ -56,7 +53,7 @@ async def import_data_activity(inputs: ImportDataActivityInputs) -> tuple[TSchem source = None if model.pipeline.source_type == ExternalDataSource.Type.STRIPE: - from posthog.temporal.data_imports.pipelines.stripe.helpers import stripe_source + from posthog.temporal.data_imports.pipelines.stripe import stripe_source stripe_secret_key = model.pipeline.job_inputs.get("stripe_secret_key", None) account_id = model.pipeline.job_inputs.get("stripe_account_id", None) @@ -65,24 +62,9 @@ async def import_data_activity(inputs: ImportDataActivityInputs) -> tuple[TSchem if not stripe_secret_key: raise ValueError(f"Stripe secret key not found for job {model.id}") - # Hacky just for specific user - region = get_instance_region() - if region == "EU" and inputs.team_id == 11870: - start_date = timezone.now().replace(day=1, hour=0, minute=0, second=0, microsecond=0) - end_date = start_date + dt.timedelta(weeks=5) - else: - start_date = None - end_date = None - + # TODO: add in check_limit to rest_source source = stripe_source( - api_key=stripe_secret_key, - account_id=account_id, - endpoints=tuple(endpoints), - team_id=inputs.team_id, - job_id=inputs.run_id, - schema_id=str(inputs.schema_id), - start_date=start_date, - end_date=end_date, + api_key=stripe_secret_key, account_id=account_id, endpoint=schema.name, is_incremental=schema.is_incremental ) return await _run(job_inputs=job_inputs, source=source, logger=logger, inputs=inputs, schema=schema) diff --git a/posthog/temporal/tests/external_data/test_external_data_job.py b/posthog/temporal/tests/external_data/test_external_data_job.py index 46ab3f2d903dfe..33363e24d5854c 100644 --- a/posthog/temporal/tests/external_data/test_external_data_job.py +++ b/posthog/temporal/tests/external_data/test_external_data_job.py @@ -1,6 +1,6 @@ import uuid from unittest import mock -from typing import Optional +from typing import Any, Optional import pytest from asgiref.sync import sync_to_async from django.test import override_settings @@ -41,12 +41,11 @@ import aioboto3 import functools from django.conf import settings +from dlt.sources.helpers.rest_client.client import RESTClient import asyncio import psycopg -from posthog.temporal.tests.utils.s3 import read_parquet_from_s3 from posthog.warehouse.models.external_data_schema import get_all_schemas_for_source_id -from posthog.warehouse.models.external_table_definitions import get_imported_fields_for_table BUCKET_NAME = "test-external-data-jobs" SESSION = aioboto3.Session() @@ -125,7 +124,7 @@ async def postgres_connection(postgres_config, setup_postgres_test_db): async def _create_schema(schema_name: str, source: ExternalDataSource, team: Team, table_id: Optional[str] = None): return await sync_to_async(ExternalDataSchema.objects.create)( name=schema_name, - team_id=team.id, + team_id=team.pk, source_id=source.pk, table_id=table_id, ) @@ -271,7 +270,7 @@ async def setup_job_1(): team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) new_job: ExternalDataJob = await sync_to_async(ExternalDataJob.objects.create)( @@ -287,7 +286,7 @@ async def setup_job_1(): inputs = ImportDataActivityInputs( team_id=team.id, - run_id=new_job.pk, + run_id=str(new_job.pk), source_id=new_source.pk, schema_id=customer_schema.id, ) @@ -302,7 +301,7 @@ async def setup_job_2(): team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) new_job: ExternalDataJob = await sync_to_async(ExternalDataJob.objects.create)( @@ -318,7 +317,7 @@ async def setup_job_2(): inputs = ImportDataActivityInputs( team_id=team.id, - run_id=new_job.pk, + run_id=str(new_job.pk), source_id=new_source.pk, schema_id=charge_schema.id, ) @@ -328,9 +327,48 @@ async def setup_job_2(): job_1, job_1_inputs = await setup_job_1() job_2, job_2_inputs = await setup_job_2() + def mock_customers_paginate( + class_self, + path: str = "", + method: Any = "GET", + params: Optional[dict[str, Any]] = None, + json: Optional[dict[str, Any]] = None, + auth: Optional[Any] = None, + paginator: Optional[Any] = None, + data_selector: Optional[Any] = None, + hooks: Optional[Any] = None, + ): + return iter( + [ + { + "id": "cus_123", + "name": "John Doe", + } + ] + ) + + def mock_charges_paginate( + class_self, + path: str = "", + method: Any = "GET", + params: Optional[dict[str, Any]] = None, + json: Optional[dict[str, Any]] = None, + auth: Optional[Any] = None, + paginator: Optional[Any] = None, + data_selector: Optional[Any] = None, + hooks: Optional[Any] = None, + ): + return iter( + [ + { + "id": "chg_123", + "customer": "cus_1", + } + ] + ) + with ( - mock.patch("stripe.Customer.list") as mock_customer_list, - mock.patch("stripe.Charge.list") as mock_charge_list, + mock.patch.object(RESTClient, "paginate", mock_customers_paginate), override_settings( BUCKET_URL=f"s3://{BUCKET_NAME}", AIRBYTE_BUCKET_KEY=settings.OBJECT_STORAGE_ACCESS_KEY_ID, @@ -341,28 +379,8 @@ async def setup_job_2(): return_value={"clickhouse": {"id": "string", "name": "string"}}, ), ): - mock_customer_list.return_value = { - "data": [ - { - "id": "cus_123", - "name": "John Doe", - } - ], - "has_more": False, - } - - mock_charge_list.return_value = { - "data": [ - { - "id": "chg_123", - "customer": "cus_1", - } - ], - "has_more": False, - } await asyncio.gather( activity_environment.run(import_data_activity, job_1_inputs), - activity_environment.run(import_data_activity, job_2_inputs), ) job_1_customer_objects = await minio_client.list_objects_v2( @@ -370,37 +388,28 @@ async def setup_job_2(): ) assert len(job_1_customer_objects["Contents"]) == 1 - s3_data = await read_parquet_from_s3( - BUCKET_NAME, - job_1_customer_objects["Contents"][0]["Key"], - {}, - settings.OBJECT_STORAGE_ACCESS_KEY_ID, - settings.OBJECT_STORAGE_SECRET_ACCESS_KEY, - ) - customer_fields = get_imported_fields_for_table("stripe_customer") - all_keys = list(s3_data[0].keys()) - assert len(s3_data) == 1 - assert all(field in all_keys for field in customer_fields) + with ( + mock.patch.object(RESTClient, "paginate", mock_charges_paginate), + override_settings( + BUCKET_URL=f"s3://{BUCKET_NAME}", + AIRBYTE_BUCKET_KEY=settings.OBJECT_STORAGE_ACCESS_KEY_ID, + AIRBYTE_BUCKET_SECRET=settings.OBJECT_STORAGE_SECRET_ACCESS_KEY, + ), + mock.patch( + "posthog.warehouse.models.table.DataWarehouseTable.get_columns", + return_value={"clickhouse": {"id": "string", "name": "string"}}, + ), + ): + await asyncio.gather( + activity_environment.run(import_data_activity, job_2_inputs), + ) job_2_charge_objects = await minio_client.list_objects_v2( Bucket=BUCKET_NAME, Prefix=f"{job_2.folder_path}/charge/" ) assert len(job_2_charge_objects["Contents"]) == 1 - s3_data = await read_parquet_from_s3( - BUCKET_NAME, - job_2_charge_objects["Contents"][0]["Key"], - {}, - settings.OBJECT_STORAGE_ACCESS_KEY_ID, - settings.OBJECT_STORAGE_SECRET_ACCESS_KEY, - ) - customer_fields = get_imported_fields_for_table("stripe_charge") - all_keys = list(s3_data[0].keys()) - - assert len(s3_data) == 1 - assert all(field in all_keys for field in customer_fields) - @pytest.mark.django_db(transaction=True) @pytest.mark.asyncio @@ -413,7 +422,7 @@ async def setup_job_1(): team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) # Already canceled so it should only run once @@ -431,7 +440,7 @@ async def setup_job_1(): inputs = ImportDataActivityInputs( team_id=team.id, - run_id=new_job.pk, + run_id=str(new_job.pk), source_id=new_source.pk, schema_id=customer_schema.id, ) @@ -440,8 +449,28 @@ async def setup_job_1(): job_1, job_1_inputs = await setup_job_1() + def mock_customers_paginate( + class_self, + path: str = "", + method: Any = "GET", + params: Optional[dict[str, Any]] = None, + json: Optional[dict[str, Any]] = None, + auth: Optional[Any] = None, + paginator: Optional[Any] = None, + data_selector: Optional[Any] = None, + hooks: Optional[Any] = None, + ): + return iter( + [ + { + "id": "cus_123", + "name": "John Doe", + } + ] + ) + with ( - mock.patch("stripe.Customer.list") as mock_customer_list, + mock.patch.object(RESTClient, "paginate", mock_customers_paginate), override_settings( BUCKET_URL=f"s3://{BUCKET_NAME}", AIRBYTE_BUCKET_KEY=settings.OBJECT_STORAGE_ACCESS_KEY_ID, @@ -452,15 +481,6 @@ async def setup_job_1(): return_value={"clickhouse": {"id": "string", "name": "string"}}, ), ): - mock_customer_list.return_value = { - "data": [ - { - "id": "cus_123", - "name": "John Doe", - } - ], - "has_more": True, - } await asyncio.gather( activity_environment.run(import_data_activity, job_1_inputs), ) @@ -487,7 +507,7 @@ async def setup_job_1(): team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) new_job: ExternalDataJob = await sync_to_async(ExternalDataJob.objects.create)( @@ -503,7 +523,7 @@ async def setup_job_1(): inputs = ImportDataActivityInputs( team_id=team.id, - run_id=new_job.pk, + run_id=str(new_job.pk), source_id=new_source.pk, schema_id=customer_schema.id, ) @@ -512,8 +532,28 @@ async def setup_job_1(): job_1, job_1_inputs = await setup_job_1() + def mock_customers_paginate( + class_self, + path: str = "", + method: Any = "GET", + params: Optional[dict[str, Any]] = None, + json: Optional[dict[str, Any]] = None, + auth: Optional[Any] = None, + paginator: Optional[Any] = None, + data_selector: Optional[Any] = None, + hooks: Optional[Any] = None, + ): + return iter( + [ + { + "id": "cus_123", + "name": "John Doe", + } + ] + ) + with ( - mock.patch("stripe.Customer.list") as mock_customer_list, + mock.patch.object(RESTClient, "paginate", mock_customers_paginate), mock.patch("posthog.temporal.data_imports.pipelines.helpers.CHUNK_SIZE", 0), override_settings( BUCKET_URL=f"s3://{BUCKET_NAME}", @@ -525,15 +565,6 @@ async def setup_job_1(): return_value={"clickhouse": {"id": "string", "name": "string"}}, ), ): - mock_customer_list.return_value = { - "data": [ - { - "id": "cus_123", - "name": "John Doe", - } - ], - "has_more": False, - } await asyncio.gather( activity_environment.run(import_data_activity, job_1_inputs), ) @@ -561,7 +592,7 @@ async def test_external_data_job_workflow_with_schema(team, **kwargs): team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) schema = await sync_to_async(ExternalDataSchema.objects.create)( @@ -657,7 +688,7 @@ async def setup_job_1(): posthog_test_schema = await _create_schema("posthog_test", new_source, team) inputs = ImportDataActivityInputs( - team_id=team.id, run_id=new_job.pk, source_id=new_source.pk, schema_id=posthog_test_schema.id + team_id=team.id, run_id=str(new_job.pk), source_id=new_source.pk, schema_id=posthog_test_schema.id ) return new_job, inputs @@ -689,7 +720,7 @@ async def test_check_schedule_activity_with_schema_id(activity_environment, team team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) test_1_schema = await _create_schema("test-1", new_source, team) @@ -716,7 +747,7 @@ async def test_check_schedule_activity_with_missing_schema_id_but_with_schedule( team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) await sync_to_async(ExternalDataSchema.objects.create)( @@ -760,7 +791,7 @@ async def test_check_schedule_activity_with_missing_schema_id_and_no_schedule(ac team=team, status="running", source_type="Stripe", - job_inputs={"stripe_secret_key": "test-key"}, + job_inputs={"stripe_secret_key": "test-key", "stripe_account_id": "acct_id"}, ) await sync_to_async(ExternalDataSchema.objects.create)( diff --git a/posthog/warehouse/models/external_table_definitions.py b/posthog/warehouse/models/external_table_definitions.py index 7030e3ccb7874a..d0e4c57e35c89d 100644 --- a/posthog/warehouse/models/external_table_definitions.py +++ b/posthog/warehouse/models/external_table_definitions.py @@ -638,7 +638,3 @@ def get_dlt_mapping_for_external_table(table): for _, field in external_tables[table].items() if type(field) != ast.ExpressionField } - - -def get_imported_fields_for_table(table): - return [field.name for _, field in external_tables[table].items() if type(field) != ast.ExpressionField] From 87c74e4d04c0f5c8dfc4b2700485344629a6fee6 Mon Sep 17 00:00:00 2001 From: Robbie Date: Mon, 10 Jun 2024 14:56:47 +0100 Subject: [PATCH 07/35] fix(web-analytics): Fix web analytics device height being too small (#22843) * Fix web analytics device height being too small * Update UI snapshots for `chromium` (2) --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- .../components/Cards/InsightCard/InsightCard.scss | 4 ++++ .../src/queries/nodes/InsightViz/InsightViz.scss | 12 ++++++++---- .../src/scenes/web-analytics/WebAnalyticsTile.tsx | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/components/Cards/InsightCard/InsightCard.scss b/frontend/src/lib/components/Cards/InsightCard/InsightCard.scss index 2773867f90eb81..06a06efbe3ef1a 100644 --- a/frontend/src/lib/components/Cards/InsightCard/InsightCard.scss +++ b/frontend/src/lib/components/Cards/InsightCard/InsightCard.scss @@ -51,6 +51,10 @@ border: none; border-radius: 0; } + + .WebAnalyticsDashboard .InsightVizDisplay & { + min-height: var(--insight-viz-min-height); + } } .InsightDetails, diff --git a/frontend/src/queries/nodes/InsightViz/InsightViz.scss b/frontend/src/queries/nodes/InsightViz/InsightViz.scss index 3d3dde0915dffb..67c5fe61452988 100644 --- a/frontend/src/queries/nodes/InsightViz/InsightViz.scss +++ b/frontend/src/queries/nodes/InsightViz/InsightViz.scss @@ -29,7 +29,8 @@ flex-direction: column; .NotebookNode &, - .InsightCard & { + .InsightCard &, + .WebAnalyticsDashboard & { flex: 1; height: 100%; @@ -102,7 +103,8 @@ } .NotebookNode &, - .InsightCard & { + .InsightCard &, + .WebAnalyticsDashboard & { .LineGraph { position: relative; min-height: 100px; @@ -119,7 +121,8 @@ margin: 0.5rem; .NotebookNode &, - .InsightCard & { + .InsightCard &, + .WebAnalyticsDashboard & { min-height: auto; } @@ -149,7 +152,8 @@ min-height: var(--insight-viz-min-height); .NotebookNode &, - .InsightCard & { + .InsightCard &, + .WebAnalyticsDashboard & { min-height: auto; } } diff --git a/frontend/src/scenes/web-analytics/WebAnalyticsTile.tsx b/frontend/src/scenes/web-analytics/WebAnalyticsTile.tsx index 62e3cce87f7fb0..6dec7396bfc179 100644 --- a/frontend/src/scenes/web-analytics/WebAnalyticsTile.tsx +++ b/frontend/src/scenes/web-analytics/WebAnalyticsTile.tsx @@ -330,7 +330,7 @@ export const WebStatsTrendTile = ({ }, [onWorldMapClick, insightProps]) return ( -
+
{showIntervalTile && (
From 4150ea208128cadd8fe30197dad7c51e77e0fd1d Mon Sep 17 00:00:00 2001 From: Marcus Hof <13001502+MarconLP@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:20:17 +0200 Subject: [PATCH 08/35] feat: allow sending users directly to the email an engineer support modal (#22807) * allow sending users directly onto the email an engineer view * Update UI snapshots for `chromium` (2) --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- frontend/src/lib/components/Support/supportLogic.ts | 13 +++++++++++-- .../src/scenes/web-analytics/WebAnalyticsNotice.tsx | 4 ++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/components/Support/supportLogic.ts b/frontend/src/lib/components/Support/supportLogic.ts index 8ada3074334c19..013eab72eb24f1 100644 --- a/frontend/src/lib/components/Support/supportLogic.ts +++ b/frontend/src/lib/components/Support/supportLogic.ts @@ -253,6 +253,7 @@ export type SupportFormFields = { target_area: SupportTicketTargetArea | null severity_level: SupportTicketSeverityLevel | null message: string + isEmailFormOpen?: boolean | 'true' | 'false' } export const supportLogic = kea([ @@ -340,13 +341,14 @@ export const supportLogic = kea([ values.sendSupportRequest.kind ?? '', values.sendSupportRequest.target_area ?? '', values.sendSupportRequest.severity_level ?? '', + values.isEmailFormOpen ?? 'false', ].join(':') if (panelOptions !== ':') { actions.setSidePanelOptions(panelOptions) } }, - openSupportForm: async ({ name, email, kind, target_area, severity_level, message }) => { + openSupportForm: async ({ name, email, isEmailFormOpen, kind, target_area, severity_level, message }) => { let area = target_area ?? getURLPathToTargetArea(window.location.pathname) if (!userLogic.values.user) { area = 'login' @@ -361,6 +363,12 @@ export const supportLogic = kea([ message: message ?? '', }) + if (isEmailFormOpen === 'true' || isEmailFormOpen === true) { + actions.openEmailForm() + } else { + actions.closeEmailForm() + } + if (values.sidePanelAvailable) { const panelOptions = [kind ?? '', area ?? ''].join(':') actions.openSidePanel(SidePanelTab.Support, panelOptions === ':' ? undefined : panelOptions) @@ -509,12 +517,13 @@ export const supportLogic = kea([ const [panel, ...panelOptions] = (hashParams['panel'] ?? '').split(':') if (panel === SidePanelTab.Support) { - const [kind, area, severity] = panelOptions + const [kind, area, severity, isEmailFormOpen] = panelOptions actions.openSupportForm({ kind: Object.keys(SUPPORT_KIND_TO_SUBJECT).includes(kind) ? kind : null, target_area: Object.keys(TARGET_AREA_TO_NAME).includes(area) ? area : null, severity_level: Object.keys(SEVERITY_LEVEL_TO_NAME).includes(severity) ? severity : null, + isEmailFormOpen: isEmailFormOpen ?? 'false', }) return } diff --git a/frontend/src/scenes/web-analytics/WebAnalyticsNotice.tsx b/frontend/src/scenes/web-analytics/WebAnalyticsNotice.tsx index 2e8727a6f5048c..fbe32a7e3b3597 100644 --- a/frontend/src/scenes/web-analytics/WebAnalyticsNotice.tsx +++ b/frontend/src/scenes/web-analytics/WebAnalyticsNotice.tsx @@ -23,14 +23,14 @@ export const WebAnalyticsNotice = (): JSX.Element => { } - onClick={() => openSupportForm({ kind: 'bug' })} + onClick={() => openSupportForm({ kind: 'bug', isEmailFormOpen: true })} > Report a bug } - onClick={() => openSupportForm({ kind: 'feedback' })} + onClick={() => openSupportForm({ kind: 'feedback', isEmailFormOpen: true })} > Give feedback From 68da0729fbcbae4d468e5db3d5444c353c342042 Mon Sep 17 00:00:00 2001 From: Julian Bez Date: Mon, 10 Jun 2024 15:32:14 +0100 Subject: [PATCH 09/35] feat(insights): Limit async query concurrency (#22796) * add a limit via redis that controls how many queries we execute in parallel * also add a limit to not execute too many for a single team. One reason is queries for the same data might slow down CH, but also it's a good idea so that no single team puts too many in the queue. * feature flag the async refresh mode in the dashboard logic * put await again so that we also for the async polling have the frontend concurrency of 2 * this is not super important on initial load, since the backend has put all async queries in the queue * but it's important when people hit the dashboard refresh button, then we want to still refresh not all at once --- frontend/src/lib/constants.tsx | 1 + .../src/scenes/dashboard/dashboardLogic.tsx | 62 +++++++------- posthog/celery.py | 6 +- posthog/clickhouse/client/limit.py | 84 +++++++++++++++++++ posthog/tasks/tasks.py | 10 ++- 5 files changed, 128 insertions(+), 35 deletions(-) create mode 100644 posthog/clickhouse/client/limit.py diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index 0d22661936fee6..6cb2f8eafc6b5f 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -167,6 +167,7 @@ export const FEATURE_FLAGS = { APPS_AND_EXPORTS_UI: 'apps-and-exports-ui', // owner: @benjackwhite HOGQL_INSIGHT_LIVE_COMPARE: 'hogql-insight-live-compare', // owner: @mariusandra HOGQL_DASHBOARD_CARDS: 'hogql-dashboard-cards', // owner: @thmsobrmlr + HOGQL_DASHBOARD_ASYNC: 'hogql-dashboard-async', // owner: @webjunkie WEBHOOKS_DENYLIST: 'webhooks-denylist', // owner: #team-pipeline PERSONS_HOGQL_QUERY: 'persons-hogql-query', // owner: @mariusandra PIPELINE_UI: 'pipeline-ui', // owner: #team-pipeline diff --git a/frontend/src/scenes/dashboard/dashboardLogic.tsx b/frontend/src/scenes/dashboard/dashboardLogic.tsx index 617c1131a810be..24cbda0dce21fa 100644 --- a/frontend/src/scenes/dashboard/dashboardLogic.tsx +++ b/frontend/src/scenes/dashboard/dashboardLogic.tsx @@ -18,6 +18,7 @@ import api, { ApiMethodOptions, getJSONOrNull } from 'lib/api' import { AUTO_REFRESH_DASHBOARD_THRESHOLD_HOURS, DashboardPrivilegeLevel, + FEATURE_FLAGS, OrganizationMembershipLevel, } from 'lib/constants' import { Dayjs, dayjs, now } from 'lib/dayjs' @@ -834,7 +835,7 @@ export const dashboardLogic = kea([ }, ], })), - events(({ actions, cache, props }) => ({ + events(({ actions, cache, props, values }) => ({ afterMount: () => { if (props.id) { if (props.dashboard) { @@ -843,7 +844,7 @@ export const dashboardLogic = kea([ actions.loadDashboardSuccess(props.dashboard) } else { actions.loadDashboard({ - refresh: 'force_cache', + refresh: values.featureFlags[FEATURE_FLAGS.HOGQL_DASHBOARD_ASYNC] ? 'async' : 'force_cache', action: 'initial_load', }) } @@ -966,7 +967,7 @@ export const dashboardLogic = kea([ const insightsToRefresh = values .sortTilesByLayout(tiles || values.insightTiles || []) .filter((t) => { - if (!initialLoad || !t.last_refresh) { + if (!initialLoad || !t.last_refresh || !!t.insight?.query_status) { return true } @@ -1016,7 +1017,13 @@ export const dashboardLogic = kea([ const queryId = `${dashboardQueryId}::${uuid()}` const queryStartTime = performance.now() const apiUrl = `api/projects/${values.currentTeamId}/insights/${insight.id}/?${toParams({ - refresh: hardRefreshWithoutCache ? 'force_blocking' : 'blocking', + refresh: values.featureFlags[FEATURE_FLAGS.HOGQL_DASHBOARD_ASYNC] + ? hardRefreshWithoutCache + ? 'force_async' + : 'async' + : hardRefreshWithoutCache + ? 'force_blocking' + : 'blocking', from_dashboard: dashboardId, // needed to load insight in correct context client_query_id: queryId, session_id: currentSessionId(), @@ -1056,29 +1063,22 @@ export const dashboardLogic = kea([ } if (refreshedInsight.query_status) { - pollForResults(refreshedInsight.query_status.id, false, methodOptions) - .then(async () => { - const apiUrl = `api/projects/${values.currentTeamId}/insights/${insight.id}/?${toParams( - { - refresh: 'async', - from_dashboard: dashboardId, // needed to load insight in correct context - client_query_id: queryId, - session_id: currentSessionId(), - } - )}` - // TODO: We get the insight again here to get everything in the right format (e.g. because of result vs results) - const refreshedInsightResponse: Response = await api.getResponse(apiUrl, methodOptions) - const refreshedInsight: InsightModel = await getJSONOrNull(refreshedInsightResponse) - dashboardsModel.actions.updateDashboardInsight( - refreshedInsight, - [], - props.id ? [props.id] : undefined - ) - actions.setRefreshStatus(insight.short_id) - }) - .catch(() => { - actions.setRefreshError(insight.short_id) - }) + await pollForResults(refreshedInsight.query_status.id, false, methodOptions) + const apiUrl = `api/projects/${values.currentTeamId}/insights/${insight.id}/?${toParams({ + refresh: 'async', + from_dashboard: dashboardId, // needed to load insight in correct context + client_query_id: queryId, + session_id: currentSessionId(), + })}` + // TODO: We get the insight again here to get everything in the right format (e.g. because of result vs results) + const polledInsightResponse: Response = await api.getResponse(apiUrl, methodOptions) + const polledInsight: InsightModel = await getJSONOrNull(polledInsightResponse) + dashboardsModel.actions.updateDashboardInsight( + polledInsight, + [], + props.id ? [props.id] : undefined + ) + actions.setRefreshStatus(insight.short_id) } else { actions.setRefreshStatus(insight.short_id) } @@ -1184,6 +1184,7 @@ export const dashboardLogic = kea([ // Initial load of actual data for dashboard items after general dashboard is fetched if ( + !values.featureFlags[FEATURE_FLAGS.HOGQL_DASHBOARD_ASYNC] && // with async we straight up want to loop through all items values.oldestRefreshed && values.oldestRefreshed.isBefore(now().subtract(AUTO_REFRESH_DASHBOARD_THRESHOLD_HOURS, 'hours')) && !process.env.STORYBOOK // allow mocking of date in storybook without triggering refresh @@ -1191,11 +1192,12 @@ export const dashboardLogic = kea([ actions.refreshAllDashboardItems({ action: 'refresh', initialLoad, dashboardQueryId }) allLoaded = false } else { - const tilesWithNoResults = values.tiles?.filter((t) => !!t.insight && !t.insight.result) || [] + const tilesWithNoOrQueuedResults = + values.tiles?.filter((t) => !!t.insight && (!t.insight.result || !!t.insight.query_status)) || [] - if (tilesWithNoResults.length) { + if (tilesWithNoOrQueuedResults.length) { actions.refreshAllDashboardItems({ - tiles: tilesWithNoResults, + tiles: tilesWithNoOrQueuedResults, action: 'load_missing', initialLoad, dashboardQueryId, diff --git a/posthog/celery.py b/posthog/celery.py index 29c45c9b607292..00c039de178643 100644 --- a/posthog/celery.py +++ b/posthog/celery.py @@ -43,7 +43,7 @@ CELERY_TASK_RETRY_COUNTER = Counter( "posthog_celery_task_retry", "task retry signal is dispatched when a task will be retried.", - labelnames=["task_name"], + labelnames=["task_name", "reason"], ) @@ -145,8 +145,8 @@ def failure_signal_handler(sender, **kwargs): @task_retry.connect -def retry_signal_handler(sender, **kwargs): - CELERY_TASK_RETRY_COUNTER.labels(task_name=sender.name).inc() +def retry_signal_handler(sender, reason, **kwargs): + CELERY_TASK_RETRY_COUNTER.labels(task_name=sender.name, reason=str(reason)).inc() @app.on_after_finalize.connect diff --git a/posthog/clickhouse/client/limit.py b/posthog/clickhouse/client/limit.py new file mode 100644 index 00000000000000..7af284451816d8 --- /dev/null +++ b/posthog/clickhouse/client/limit.py @@ -0,0 +1,84 @@ +import time +from functools import wraps +from typing import Optional +from collections.abc import Callable + +from celery import current_task +from prometheus_client import Counter + +from posthog import redis + +CONCURRENT_TASKS_LIMIT_EXCEEDED_COUNTER = Counter( + "posthog_celery_task_concurrency_limit_exceeded", + "Number of times a Celery task exceeded the concurrency limit", + ["task_name", "limit", "key"], +) + +# Lua script for atomic check, remove expired if limit hit, and increment with TTL +lua_script = """ +local key = KEYS[1] +local current_time = tonumber(ARGV[1]) +local task_id = ARGV[2] +local max_concurrent_tasks = tonumber(ARGV[3]) +local ttl = tonumber(ARGV[4]) +local expiration_time = current_time + ttl + +-- Check the number of current running tasks +local running_tasks_count = redis.call('ZCARD', key) +if running_tasks_count >= max_concurrent_tasks then + -- Remove expired tasks if limit is hit + redis.call('ZREMRANGEBYSCORE', key, '-inf', current_time) + running_tasks_count = redis.call('ZCARD', key) + if running_tasks_count >= max_concurrent_tasks then + return 0 + end +end + +-- Add the new task with its expiration time +redis.call('ZADD', key, expiration_time, task_id) +return 1 +""" + + +class CeleryConcurrencyLimitExceeded(Exception): + pass + + +def limit_concurrency(max_concurrent_tasks: int, key: Optional[Callable] = None, ttl: int = 60 * 15) -> Callable: + def decorator(task_func): + @wraps(task_func) + def wrapper(*args, **kwargs): + task_name = current_task.name + redis_client = redis.get_client() + running_tasks_key = f"celery_running_tasks:{task_name}" + if key: + dynamic_key = key(*args, **kwargs) + running_tasks_key = f"{running_tasks_key}:{dynamic_key}" + else: + dynamic_key = None + task_id = f"{task_name}:{current_task.request.id}" + current_time = int(time.time()) + + # Atomically check, remove expired if limit hit, and add the new task + if ( + redis_client.eval(lua_script, 1, running_tasks_key, current_time, task_id, max_concurrent_tasks, ttl) + == 0 + ): + CONCURRENT_TASKS_LIMIT_EXCEEDED_COUNTER.labels( + task_name=task_name, limit=max_concurrent_tasks, key=dynamic_key + ).inc() + + raise CeleryConcurrencyLimitExceeded( + f"Exceeded maximum concurrent tasks limit: {max_concurrent_tasks} for key: {dynamic_key}" + ) + + try: + # Execute the task + return task_func(*args, **kwargs) + finally: + # Remove the task ID from the sorted set when the task finishes + redis_client.zrem(running_tasks_key, task_id) + + return wrapper + + return decorator diff --git a/posthog/tasks/tasks.py b/posthog/tasks/tasks.py index 3e64c3fe7fc6fb..d337c5827630d3 100644 --- a/posthog/tasks/tasks.py +++ b/posthog/tasks/tasks.py @@ -10,6 +10,7 @@ from redis import Redis from structlog import get_logger +from posthog.clickhouse.client.limit import limit_concurrency, CeleryConcurrencyLimitExceeded from posthog.cloud_utils import is_cloud from posthog.errors import CHQueryErrorTooManySimultaneousQueries from posthog.hogql.constants import LimitContext @@ -40,11 +41,17 @@ def redis_heartbeat() -> None: autoretry_for=( # Important: Only retry for things that might be okay on the next try CHQueryErrorTooManySimultaneousQueries, + CeleryConcurrencyLimitExceeded, ), retry_backoff=1, - retry_backoff_max=2, + retry_backoff_max=10, max_retries=3, + expires=60 * 10, # Do not run queries that got stuck for more than this ) +@limit_concurrency(90) # Do not go above what CH can handle (max_concurrent_queries) +@limit_concurrency( + 10, key=lambda *args, **kwargs: kwargs.get("team_id") or args[0] +) # Do not run too many queries at once for the same team def process_query_task( team_id: int, user_id: Optional[int], @@ -173,7 +180,6 @@ def pg_row_count() -> None: "log_entries", ] - HEARTBEAT_EVENT_TO_INGESTION_LAG_METRIC = { "heartbeat": "ingestion", "heartbeat_buffer": "ingestion_buffer", From 93b4dd3f2f23abc09c26e5dcfe210e76039f7bb4 Mon Sep 17 00:00:00 2001 From: Frank Hamand Date: Mon, 10 Jun 2024 15:38:25 +0100 Subject: [PATCH 10/35] feat: use new deployment trigger for temporal worker deployments (#22842) * feat: use new deployment trigger for temporal worker deploys This reverts commit 653798c7e3a67a281e22b360f524011712002dde. * noop change to trigger deploy --- .github/workflows/container-images-cd.yml | 63 ++++++++++++------- .../commands/start_temporal_worker.py | 1 - 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/.github/workflows/container-images-cd.yml b/.github/workflows/container-images-cd.yml index 9611588bc1700c..e1ef69937452d2 100644 --- a/.github/workflows/container-images-cd.yml +++ b/.github/workflows/container-images-cd.yml @@ -120,17 +120,22 @@ jobs: - name: Trigger Batch Exports Temporal Worker Cloud deployment if: steps.check_changes_batch_exports_temporal_worker.outputs.changed == 'true' - uses: mvasigh/dispatch-action@main + uses: peter-evans/repository-dispatch@v3 with: token: ${{ steps.deployer.outputs.token }} - repo: charts - owner: PostHog - event_type: temporal_worker_deploy - message: | + repository: PostHog/charts + event-type: commit_state_update + client-payload: | { - "image_tag": "${{ steps.build.outputs.digest }}", - "worker_name": "temporal-worker", - "context": ${{ toJson(github) }} + "values": { + "image": { + "sha": "${{ steps.build.outputs.digest }}" + } + }, + "release": "temporal-worker", + "commit": ${{ toJson(github.event.head_commit) }}, + "repository": ${{ toJson(github.repository) }}, + "labels": ${{ steps.labels.outputs.labels }} } - name: Check for changes that affect general purpose temporal worker @@ -140,17 +145,22 @@ jobs: - name: Trigger General Purpose Temporal Worker Cloud deployment if: steps.check_changes_general_purpose_temporal_worker.outputs.changed == 'true' - uses: mvasigh/dispatch-action@main + uses: peter-evans/repository-dispatch@v3 with: token: ${{ steps.deployer.outputs.token }} - repo: charts - owner: PostHog - event_type: temporal_worker_deploy - message: | + repository: PostHog/charts + event-type: commit_state_update + client-payload: | { - "image_tag": "${{ steps.build.outputs.digest }}", - "worker_name": "temporal-worker-general-purpose", - "context": ${{ toJson(github) }} + "values": { + "image": { + "sha": "${{ steps.build.outputs.digest }}" + } + }, + "release": "temporal-worker-general-purpose", + "commit": ${{ toJson(github.event.head_commit) }}, + "repository": ${{ toJson(github.repository) }}, + "labels": ${{ steps.labels.outputs.labels }} } - name: Check for changes that affect data warehouse temporal worker @@ -160,15 +170,20 @@ jobs: - name: Trigger Data Warehouse Temporal Worker Cloud deployment if: steps.check_changes_data_warehouse_temporal_worker.outputs.changed == 'true' - uses: mvasigh/dispatch-action@main + uses: peter-evans/repository-dispatch@v3 with: token: ${{ steps.deployer.outputs.token }} - repo: charts - owner: PostHog - event_type: temporal_worker_deploy - message: | + repository: PostHog/charts + event-type: commit_state_update + client-payload: | { - "image_tag": "${{ steps.build.outputs.digest }}", - "worker_name": "temporal-worker-data-warehouse", - "context": ${{ toJson(github) }} + "values": { + "image": { + "sha": "${{ steps.build.outputs.digest }}" + } + }, + "release": "temporal-worker-data-warehouse", + "commit": ${{ toJson(github.event.head_commit) }}, + "repository": ${{ toJson(github.repository) }}, + "labels": ${{ steps.labels.outputs.labels }} } diff --git a/posthog/management/commands/start_temporal_worker.py b/posthog/management/commands/start_temporal_worker.py index 3fb2e0444e87fc..1b79e594323e7d 100644 --- a/posthog/management/commands/start_temporal_worker.py +++ b/posthog/management/commands/start_temporal_worker.py @@ -92,7 +92,6 @@ def handle(self, *args, **options): logging.info(f"Starting Temporal Worker with options: {options}") structlog.reset_defaults() - metrics_port = int(options["metrics_port"]) asyncio.run( From e865a3303d9692583318e4d024d5cfbf23bb2eb3 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 10 Jun 2024 15:48:45 +0100 Subject: [PATCH 11/35] fix: Permission check (#22851) --- posthog/permissions.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/posthog/permissions.py b/posthog/permissions.py index bf4054ef1cb8d1..72fd657a9ad4f0 100644 --- a/posthog/permissions.py +++ b/posthog/permissions.py @@ -409,7 +409,7 @@ def get_scope_object(self, request, view) -> APIScopeObjectOrNotSupported: class PostHogFeatureFlagPermission(BasePermission): def has_permission(self, request, view) -> bool: - user = cast(request.user, User) + user = cast(User, request.user) organization = get_organization_from_view(view) flag = getattr(view, "posthog_feature_flag", None) @@ -427,11 +427,13 @@ def has_permission(self, request, view) -> bool: for required_flag, actions in config.items(): if "*" in actions or view.action in actions: + org_id = str(organization.id) + enabled = posthoganalytics.feature_enabled( required_flag, - str(user.distinct_id), - groups={"organization": str(organization.id)}, - group_properties={"organization": {"id": str(organization.id)}}, + user.distinct_id, + groups={"organization": org_id}, + group_properties={"organization": {"id": org_id}}, only_evaluate_locally=False, send_feature_flag_events=False, ) From 2e0406a551011a28a87061284443dd0a8d885761 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 10 Jun 2024 09:39:51 -0600 Subject: [PATCH 12/35] chore(plugin-server): change captureIngestionWarning to not await acks (#22850) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(plugin-server): change captureIngestionWarning to not await acks * chore(plugin-server): move person kafka ack awaits to the batch-level… (#22772) chore(plugin-server): move person kafka ack awaits to the batch-level await --- plugin-server/src/utils/db/db.ts | 10 +- .../event-pipeline/processPersonsStep.ts | 6 +- .../worker/ingestion/event-pipeline/runner.ts | 27 ++-- .../src/worker/ingestion/person-state.ts | 103 +++++++------- plugin-server/src/worker/ingestion/utils.ts | 2 +- plugin-server/tests/main/db.test.ts | 14 +- ...events-ingestion-overflow-consumer.test.ts | 2 +- .../session-recording/utils.test.ts | 4 +- .../tests/main/process-event.test.ts | 13 +- .../worker/ingestion/person-state.test.ts | 127 ++++++++++++------ .../worker/ingestion/postgres-parity.test.ts | 14 +- 11 files changed, 200 insertions(+), 122 deletions(-) diff --git a/plugin-server/src/utils/db/db.ts b/plugin-server/src/utils/db/db.ts index 500044a815e909..a7cd6d0b23dd9f 100644 --- a/plugin-server/src/utils/db/db.ts +++ b/plugin-server/src/utils/db/db.ts @@ -754,20 +754,14 @@ export class DB { personUpdateVersionMismatchCounter.inc() } - const kafkaMessages = [] - const message = generateKafkaPersonUpdateMessage(updatedPerson) - if (tx) { - kafkaMessages.push(message) - } else { - await this.kafkaProducer.queueMessage({ kafkaMessage: message, waitForAck: true }) - } + const kafkaMessage = generateKafkaPersonUpdateMessage(updatedPerson) status.debug( '🧑‍🦰', `Updated person ${updatedPerson.uuid} of team ${updatedPerson.team_id} to version ${updatedPerson.version}.` ) - return [updatedPerson, kafkaMessages] + return [updatedPerson, [kafkaMessage]] } public async deletePerson(person: InternalPerson, tx?: TransactionClient): Promise { diff --git a/plugin-server/src/worker/ingestion/event-pipeline/processPersonsStep.ts b/plugin-server/src/worker/ingestion/event-pipeline/processPersonsStep.ts index a0978497d7e34b..7f2e359e2e0173 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/processPersonsStep.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/processPersonsStep.ts @@ -10,13 +10,13 @@ export async function processPersonsStep( event: PluginEvent, timestamp: DateTime, processPerson: boolean -): Promise<[PluginEvent, Person]> { +): Promise<[PluginEvent, Person, Promise]> { let overridesWriter: DeferredPersonOverrideWriter | undefined = undefined if (runner.poEEmbraceJoin) { overridesWriter = new DeferredPersonOverrideWriter(runner.hub.db.postgres) } - const person = await new PersonState( + const [person, kafkaAck] = await new PersonState( event, event.team_id, String(event.distinct_id), @@ -27,5 +27,5 @@ export async function processPersonsStep( overridesWriter ).update() - return [event, person] + return [event, person, kafkaAck] } diff --git a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts index 52e762949a9240..26c645e5089cac 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/runner.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/runner.ts @@ -175,19 +175,17 @@ export class EventPipelineRunner { } if (event.event === '$$client_ingestion_warning') { - kafkaAcks.push( - captureIngestionWarning( - this.hub.db.kafkaProducer, - event.team_id, - 'client_ingestion_warning', - { - eventUuid: event.uuid, - event: event.event, - distinctId: event.distinct_id, - message: event.properties?.$$client_ingestion_warning_message, - }, - { alwaysSend: true } - ) + await captureIngestionWarning( + this.hub.db.kafkaProducer, + event.team_id, + 'client_ingestion_warning', + { + eventUuid: event.uuid, + event: event.event, + distinctId: event.distinct_id, + message: event.properties?.$$client_ingestion_warning_message, + }, + { alwaysSend: true } ) return this.registerLastStep('clientIngestionWarning', [event], kafkaAcks) @@ -205,11 +203,12 @@ export class EventPipelineRunner { event.team_id ) - const [postPersonEvent, person] = await this.runStep( + const [postPersonEvent, person, personKafkaAck] = await this.runStep( processPersonsStep, [this, normalizedEvent, timestamp, processPerson], event.team_id ) + kafkaAcks.push(personKafkaAck) const preparedEvent = await this.runStep( prepareEventStep, diff --git a/plugin-server/src/worker/ingestion/person-state.ts b/plugin-server/src/worker/ingestion/person-state.ts index 3f9cfa2f9d3b65..60202bf2a5900c 100644 --- a/plugin-server/src/worker/ingestion/person-state.ts +++ b/plugin-server/src/worker/ingestion/person-state.ts @@ -102,7 +102,7 @@ export class PersonState { this.updateIsIdentified = false } - async update(): Promise { + async update(): Promise<[Person, Promise]> { if (!this.processPerson) { if (this.lazyPersonCreation) { const existingPerson = await this.db.fetchPerson(this.teamId, this.distinctId, { useReadReplica: true }) @@ -122,7 +122,7 @@ export class PersonState { person.force_upgrade = true } - return person + return [person, Promise.resolve()] } // We need a value from the `person_created_column` in ClickHouse. This should be @@ -137,7 +137,7 @@ export class PersonState { uuid: uuidFromDistinctId(this.teamId, this.distinctId), created_at: createdAt, } - return fakePerson + return [fakePerson, Promise.resolve()] } else { // We don't need to handle any properties for `processPerson=false` events, so we can // short circuit by just finding or creating a person and returning early. @@ -146,24 +146,29 @@ export class PersonState { // Ensure person properties don't propagate elsewhere, such as onto the event itself. person.properties = {} - return person + return [person, Promise.resolve()] } } - const person: InternalPerson | undefined = await this.handleIdentifyOrAlias() // TODO: make it also return a boolean for if we can exit early here + const [person, identifyOrAliasKafkaAck]: [InternalPerson | undefined, Promise] = + await this.handleIdentifyOrAlias() // TODO: make it also return a boolean for if we can exit early here + if (person) { // try to shortcut if we have the person from identify or alias try { - return await this.updatePersonProperties(person) + const [updatedPerson, updateKafkaAck] = await this.updatePersonProperties(person) + return [updatedPerson, Promise.all([identifyOrAliasKafkaAck, updateKafkaAck]).then(() => undefined)] } catch (error) { // shortcut didn't work, swallow the error and try normal retry loop below status.debug('🔁', `failed update after adding distinct IDs, retrying`, { error }) } } - return await this.handleUpdate() + + const [updatedPerson, updateKafkaAck] = await this.handleUpdate() + return [updatedPerson, Promise.all([identifyOrAliasKafkaAck, updateKafkaAck]).then(() => undefined)] } - async handleUpdate(): Promise { + async handleUpdate(): Promise<[InternalPerson, Promise]> { // There are various reasons why update can fail: // - anothe thread created the person during a race // - the person might have been merged between start of processing and now @@ -171,10 +176,10 @@ export class PersonState { return await promiseRetry(() => this.updateProperties(), 'update_person') } - async updateProperties(): Promise { + async updateProperties(): Promise<[InternalPerson, Promise]> { const [person, propertiesHandled] = await this.createOrGetPerson() if (propertiesHandled) { - return person + return [person, Promise.resolve()] } return await this.updatePersonProperties(person) } @@ -251,7 +256,7 @@ export class PersonState { ) } - private async updatePersonProperties(person: InternalPerson): Promise { + private async updatePersonProperties(person: InternalPerson): Promise<[InternalPerson, Promise]> { person.properties ||= {} const update: Partial = {} @@ -263,10 +268,12 @@ export class PersonState { } if (Object.keys(update).length > 0) { - // Note: we're not passing the client, so kafka messages are waited for within the function - ;[person] = await this.db.updatePersonDeprecated(person, update) + const [updatedPerson, kafkaMessages] = await this.db.updatePersonDeprecated(person, update) + const kafkaAck = this.db.kafkaProducer.queueMessages({ kafkaMessages, waitForAck: true }) + return [updatedPerson, kafkaAck] } - return person + + return [person, Promise.resolve()] } /** @@ -306,7 +313,7 @@ export class PersonState { // Alias & merge - async handleIdentifyOrAlias(): Promise { + async handleIdentifyOrAlias(): Promise<[InternalPerson | undefined, Promise]> { /** * strategy: * - if the two distinct ids passed don't match and aren't illegal, then mark `is_identified` to be true for the `distinct_id` person @@ -350,7 +357,7 @@ export class PersonState { } finally { clearTimeout(timeout) } - return undefined + return [undefined, Promise.resolve()] } public async merge( @@ -358,10 +365,10 @@ export class PersonState { mergeIntoDistinctId: string, teamId: number, timestamp: DateTime - ): Promise { + ): Promise<[InternalPerson | undefined, Promise]> { // No reason to alias person against itself. Done by posthog-node when updating user properties if (mergeIntoDistinctId === otherPersonDistinctId) { - return undefined + return [undefined, Promise.resolve()] } if (isDistinctIdIllegal(mergeIntoDistinctId)) { await captureIngestionWarning( @@ -375,7 +382,7 @@ export class PersonState { }, { alwaysSend: true } ) - return undefined + return [undefined, Promise.resolve()] } if (isDistinctIdIllegal(otherPersonDistinctId)) { await captureIngestionWarning( @@ -389,7 +396,7 @@ export class PersonState { }, { alwaysSend: true } ) - return undefined + return [undefined, Promise.resolve()] } return promiseRetry( () => this.mergeDistinctIds(otherPersonDistinctId, mergeIntoDistinctId, teamId, timestamp), @@ -402,7 +409,7 @@ export class PersonState { mergeIntoDistinctId: string, teamId: number, timestamp: DateTime - ): Promise { + ): Promise<[InternalPerson, Promise]> { this.updateIsIdentified = true const otherPerson = await this.db.fetchPerson(teamId, otherPersonDistinctId) @@ -429,13 +436,13 @@ export class PersonState { if (otherPerson && !mergeIntoPerson) { await this.db.addDistinctId(otherPerson, mergeIntoDistinctId, addDistinctIdVersion) - return otherPerson + return [otherPerson, Promise.resolve()] } else if (!otherPerson && mergeIntoPerson) { await this.db.addDistinctId(mergeIntoPerson, otherPersonDistinctId, addDistinctIdVersion) - return mergeIntoPerson + return [mergeIntoPerson, Promise.resolve()] } else if (otherPerson && mergeIntoPerson) { if (otherPerson.id == mergeIntoPerson.id) { - return mergeIntoPerson + return [mergeIntoPerson, Promise.resolve()] } return await this.mergePeople({ mergeInto: mergeIntoPerson, @@ -446,18 +453,21 @@ export class PersonState { } // The last case: (!oldPerson && !newPerson) - return await this.createPerson( - // TODO: in this case we could skip the properties updates later - timestamp, - this.eventProperties['$set'] || {}, - this.eventProperties['$set_once'] || {}, - teamId, - null, - true, - this.event.uuid, - [mergeIntoDistinctId, otherPersonDistinctId], - addDistinctIdVersion - ) + return [ + await this.createPerson( + // TODO: in this case we could skip the properties updates later + timestamp, + this.eventProperties['$set'] || {}, + this.eventProperties['$set_once'] || {}, + teamId, + null, + true, + this.event.uuid, + [mergeIntoDistinctId, otherPersonDistinctId], + addDistinctIdVersion + ), + Promise.resolve(), + ] } public async mergePeople({ @@ -470,7 +480,7 @@ export class PersonState { mergeIntoDistinctId: string otherPerson: InternalPerson otherPersonDistinctId: string - }): Promise { + }): Promise<[InternalPerson, Promise]> { const olderCreatedAt = DateTime.min(mergeInto.created_at, otherPerson.created_at) const mergeAllowed = this.isMergeAllowed(otherPerson) @@ -488,7 +498,7 @@ export class PersonState { { alwaysSend: true } ) status.warn('🤔', 'refused to merge an already identified user via an $identify or $create_alias call') - return mergeInto // We're returning the original person tied to distinct_id used for the event + return [mergeInto, Promise.resolve()] // We're returning the original person tied to distinct_id used for the event } // How the merge works: @@ -508,14 +518,14 @@ export class PersonState { const properties: Properties = { ...otherPerson.properties, ...mergeInto.properties } this.applyEventPropertyUpdates(properties) - const [kafkaMessages, mergedPerson] = await this.handleMergeTransaction( + const [mergedPerson, kafkaMessages] = await this.handleMergeTransaction( mergeInto, otherPerson, olderCreatedAt, // Keep the oldest created_at (i.e. the first time we've seen either person) properties ) - await this.db.kafkaProducer.queueMessages({ kafkaMessages, waitForAck: true }) - return mergedPerson + + return [mergedPerson, kafkaMessages] } private isMergeAllowed(mergeFrom: InternalPerson): boolean { @@ -529,7 +539,7 @@ export class PersonState { otherPerson: InternalPerson, createdAt: DateTime, properties: Properties - ): Promise<[ProducerRecord[], InternalPerson]> { + ): Promise<[InternalPerson, Promise]> { mergeTxnAttemptCounter .labels({ call: this.event.event, // $identify, $create_alias or $merge_dangerously @@ -539,7 +549,7 @@ export class PersonState { }) .inc() - const result: [ProducerRecord[], InternalPerson] = await this.db.postgres.transaction( + const [mergedPerson, kafkaMessages]: [InternalPerson, ProducerRecord[]] = await this.db.postgres.transaction( PostgresUse.COMMON_WRITE, 'mergePeople', async (tx) => { @@ -573,7 +583,7 @@ export class PersonState { ) } - return [[...updatePersonMessages, ...distinctIdMessages, ...deletePersonMessages], person] + return [person, [...updatePersonMessages, ...distinctIdMessages, ...deletePersonMessages]] } ) @@ -585,7 +595,10 @@ export class PersonState { poEEmbraceJoin: String(!!this.personOverrideWriter), }) .inc() - return result + + const kafkaAck = this.db.kafkaProducer.queueMessages({ kafkaMessages, waitForAck: true }) + + return [mergedPerson, kafkaAck] } } diff --git a/plugin-server/src/worker/ingestion/utils.ts b/plugin-server/src/worker/ingestion/utils.ts index 9488ee759581b4..c6c313d74d4593 100644 --- a/plugin-server/src/worker/ingestion/utils.ts +++ b/plugin-server/src/worker/ingestion/utils.ts @@ -94,7 +94,7 @@ export async function captureIngestionWarning( }, ], }, - waitForAck: true, + waitForAck: false, }) } else { return Promise.resolve() diff --git a/plugin-server/tests/main/db.test.ts b/plugin-server/tests/main/db.test.ts index e5c08a092099b5..94b8d8d3103633 100644 --- a/plugin-server/tests/main/db.test.ts +++ b/plugin-server/tests/main/db.test.ts @@ -360,7 +360,11 @@ describe('DB', () => { const personProvided = { ...personDbBefore, properties: { c: 'bbb' }, created_at: providedPersonTs } const updateTs = DateTime.fromISO('2000-04-04T11:42:06.502Z').toUTC() const update = { created_at: updateTs } - const [updatedPerson] = await db.updatePersonDeprecated(personProvided, update) + const [updatedPerson, kafkaMessages] = await db.updatePersonDeprecated(personProvided, update) + await hub.db.kafkaProducer.queueMessages({ + kafkaMessages, + waitForAck: true, + }) // verify we have the correct update in Postgres db const personDbAfter = await fetchPersonByPersonId(personDbBefore.team_id, personDbBefore.id) @@ -418,7 +422,13 @@ describe('DB', () => { await delayUntilEventIngested(fetchPersonsRows, 1) // We do an update to verify - await db.updatePersonDeprecated(person, { properties: { foo: 'bar' } }) + const [_p, updatePersonKafkaMessages] = await db.updatePersonDeprecated(person, { + properties: { foo: 'bar' }, + }) + await hub.db.kafkaProducer.queueMessages({ + kafkaMessages: updatePersonKafkaMessages, + waitForAck: true, + }) await db.kafkaProducer.flush() await delayUntilEventIngested(fetchPersonsRows, 2) diff --git a/plugin-server/tests/main/ingestion-queues/analytics-events-ingestion-overflow-consumer.test.ts b/plugin-server/tests/main/ingestion-queues/analytics-events-ingestion-overflow-consumer.test.ts index 774475a5b34aa3..e0f46c4a39d31f 100644 --- a/plugin-server/tests/main/ingestion-queues/analytics-events-ingestion-overflow-consumer.test.ts +++ b/plugin-server/tests/main/ingestion-queues/analytics-events-ingestion-overflow-consumer.test.ts @@ -113,7 +113,7 @@ describe('eachBatchParallelIngestion with overflow consume', () => { }, ], }, - waitForAck: true, + waitForAck: false, }) // Event is processed diff --git a/plugin-server/tests/main/ingestion-queues/session-recording/utils.test.ts b/plugin-server/tests/main/ingestion-queues/session-recording/utils.test.ts index edd4f95bebda27..6678c28d92a3ef 100644 --- a/plugin-server/tests/main/ingestion-queues/session-recording/utils.test.ts +++ b/plugin-server/tests/main/ingestion-queues/session-recording/utils.test.ts @@ -221,7 +221,7 @@ describe('session-recording utils', () => { ], topic: 'clickhouse_ingestion_warnings_test', }, - waitForAck: true, + waitForAck: false, }, ], ], @@ -241,7 +241,7 @@ describe('session-recording utils', () => { ], topic: 'clickhouse_ingestion_warnings_test', }, - waitForAck: true, + waitForAck: false, }, ], ], diff --git a/plugin-server/tests/main/process-event.test.ts b/plugin-server/tests/main/process-event.test.ts index 71432287879c34..f5a9576d07c7cf 100644 --- a/plugin-server/tests/main/process-event.test.ts +++ b/plugin-server/tests/main/process-event.test.ts @@ -209,11 +209,20 @@ test('merge people', async () => { const p0 = await createPerson(hub, team, ['person_0'], { $os: 'Microsoft' }) await delayUntilEventIngested(() => hub.db.fetchPersons(Database.ClickHouse), 1) - await hub.db.updatePersonDeprecated(p0, { created_at: DateTime.fromISO('2020-01-01T00:00:00Z') }) + const [_person0, kafkaMessages0] = await hub.db.updatePersonDeprecated(p0, { + created_at: DateTime.fromISO('2020-01-01T00:00:00Z'), + }) const p1 = await createPerson(hub, team, ['person_1'], { $os: 'Chrome', $browser: 'Chrome' }) await delayUntilEventIngested(() => hub.db.fetchPersons(Database.ClickHouse), 2) - await hub.db.updatePersonDeprecated(p1, { created_at: DateTime.fromISO('2019-07-01T00:00:00Z') }) + const [_person1, kafkaMessages1] = await hub.db.updatePersonDeprecated(p1, { + created_at: DateTime.fromISO('2019-07-01T00:00:00Z'), + }) + + await hub.db.kafkaProducer.queueMessages({ + kafkaMessages: [...kafkaMessages0, ...kafkaMessages1], + waitForAck: true, + }) await processEvent( 'person_1', diff --git a/plugin-server/tests/worker/ingestion/person-state.test.ts b/plugin-server/tests/worker/ingestion/person-state.test.ts index d3a04018e0d964..0c363f4f75d610 100644 --- a/plugin-server/tests/worker/ingestion/person-state.test.ts +++ b/plugin-server/tests/worker/ingestion/person-state.test.ts @@ -164,7 +164,7 @@ describe('PersonState.update()', () => { it('creates deterministic person uuids that are different between teams', async () => { const event_uuid = new UUIDT().toString() const primaryTeamId = teamId - const personPrimaryTeam = await personState({ + const [personPrimaryTeam, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, uuid: event_uuid, @@ -172,13 +172,15 @@ describe('PersonState.update()', () => { const otherTeamId = await createTeam(hub.db.postgres, organizationId) teamId = otherTeamId - const personOtherTeam = await personState({ + const [personOtherTeam, kafkaAcksOther] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, uuid: event_uuid, }).updateProperties() await hub.db.kafkaProducer.flush() + await kafkaAcks + await kafkaAcksOther expect(personPrimaryTeam.uuid).toEqual(uuidFromDistinctId(primaryTeamId, newUserDistinctId)) expect(personOtherTeam.uuid).toEqual(uuidFromDistinctId(otherTeamId, newUserDistinctId)) @@ -191,7 +193,7 @@ describe('PersonState.update()', () => { const hubParam = undefined const processPerson = false const lazyPersonCreation = true - const fakePerson = await personState( + const [fakePerson, kafkaAcks] = await personState( { event: '$pageview', distinct_id: newUserDistinctId, @@ -203,6 +205,7 @@ describe('PersonState.update()', () => { lazyPersonCreation ).update() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(fakePerson).toEqual( expect.objectContaining({ @@ -229,7 +232,7 @@ describe('PersonState.update()', () => { const hubParam = undefined let processPerson = true const lazyPersonCreation = true - await personState( + const [_person, kafkaAcks] = await personState( { event: '$identify', distinct_id: newUserDistinctId, @@ -242,6 +245,7 @@ describe('PersonState.update()', () => { lazyPersonCreation ).update() await hub.db.kafkaProducer.flush() + await kafkaAcks await delayUntilEventIngested(() => fetchOverridesForDistinctId(newUserDistinctId)) const chOverrides = await fetchOverridesForDistinctId(newUserDistinctId) @@ -263,7 +267,7 @@ describe('PersonState.update()', () => { processPerson = false const event_uuid = new UUIDT().toString() const timestampParam = timestamp.plus({ minutes: 5 }) // Event needs to happen after Person creation - const fakePerson = await personState( + const [fakePerson, kafkaAcks2] = await personState( { event: '$pageview', distinct_id: newUserDistinctId, @@ -276,6 +280,7 @@ describe('PersonState.update()', () => { timestampParam ).update() await hub.db.kafkaProducer.flush() + await kafkaAcks2 expect(fakePerson).toEqual( expect.objectContaining({ @@ -290,7 +295,7 @@ describe('PersonState.update()', () => { it('creates person if they are new', async () => { const event_uuid = new UUIDT().toString() - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, uuid: event_uuid, @@ -298,6 +303,7 @@ describe('PersonState.update()', () => { properties: { $set: { null_byte: '\u0000' } }, }).updateProperties() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -328,7 +334,7 @@ describe('PersonState.update()', () => { // *not* created here. const event_uuid = new UUIDT().toString() const processPerson = false - const person = await personState( + const [person, kafkaAcks] = await personState( { event: '$pageview', distinct_id: newUserDistinctId, @@ -339,6 +345,7 @@ describe('PersonState.update()', () => { processPerson ).update() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -368,13 +375,14 @@ describe('PersonState.update()', () => { it('does not attach existing person properties to $process_person_profile=false events', async () => { const originalEventUuid = new UUIDT().toString() - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, uuid: originalEventUuid, properties: { $set: { c: 420 } }, }).update() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -398,7 +406,7 @@ describe('PersonState.update()', () => { // OK, a person now exists with { c: 420 }, let's prove the properties come back out // of the DB. - const personVerifyProps = await personState({ + const [personVerifyProps] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, uuid: new UUIDT().toString(), @@ -407,7 +415,7 @@ describe('PersonState.update()', () => { expect(personVerifyProps.properties).toEqual({ $creator_event_uuid: originalEventUuid, c: 420 }) // But they don't when $process_person_profile=false - const processPersonFalseResult = await personState( + const [processPersonFalseResult] = await personState( { event: '$pageview', distinct_id: newUserDistinctId, @@ -427,8 +435,12 @@ describe('PersonState.update()', () => { return Promise.resolve(undefined) }) - const person = await personState({ event: '$pageview', distinct_id: newUserDistinctId }).handleUpdate() + const [person, kafkaAcks] = await personState({ + event: '$pageview', + distinct_id: newUserDistinctId, + }).handleUpdate() await hub.db.kafkaProducer.flush() + await kafkaAcks // if creation fails we should return the person that another thread already created expect(person).toEqual( @@ -461,7 +473,7 @@ describe('PersonState.update()', () => { return Promise.resolve(undefined) }) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, properties: { @@ -470,6 +482,7 @@ describe('PersonState.update()', () => { }, }).handleUpdate() await hub.db.kafkaProducer.flush() + await kafkaAcks // if creation fails we should return the person that another thread already created expect(person).toEqual( @@ -494,7 +507,7 @@ describe('PersonState.update()', () => { }) it('creates person with properties', async () => { - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, properties: { @@ -503,6 +516,7 @@ describe('PersonState.update()', () => { }, }).updateProperties() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -543,7 +557,7 @@ describe('PersonState.update()', () => { [newUserDistinctId] ) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, properties: { @@ -552,6 +566,7 @@ describe('PersonState.update()', () => { }, }).updateProperties() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -594,9 +609,12 @@ describe('PersonState.update()', () => { $set: { b: 4 }, }, }) - jest.spyOn(personS, 'handleIdentifyOrAlias').mockReturnValue(Promise.resolve(personInitial)) - const person = await personS.update() + jest.spyOn(personS, 'handleIdentifyOrAlias').mockReturnValue( + Promise.resolve([personInitial, Promise.resolve()]) + ) + const [person, kafkaAcks] = await personS.update() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -622,7 +640,7 @@ describe('PersonState.update()', () => { newUserDistinctId, ]) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$pageview', distinct_id: newUserDistinctId, properties: { @@ -631,6 +649,7 @@ describe('PersonState.update()', () => { }, }).updateProperties() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -661,8 +680,9 @@ describe('PersonState.update()', () => { }) personS.updateIsIdentified = true - const person = await personS.updateProperties() + const [person, kafkaAcks] = await personS.updateProperties() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ id: expect.any(Number), @@ -713,10 +733,13 @@ describe('PersonState.update()', () => { distinct_id: newUserDistinctId, properties: { $set: { a: 7, d: 9 } }, }) - jest.spyOn(personS, 'handleIdentifyOrAlias').mockReturnValue(Promise.resolve(mergeDeletedPerson)) + jest.spyOn(personS, 'handleIdentifyOrAlias').mockReturnValue( + Promise.resolve([mergeDeletedPerson, Promise.resolve()]) + ) - const person = await personS.update() + const [person, kafkaAcks] = await personS.update() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -746,7 +769,7 @@ describe('PersonState.update()', () => { describe(`overrides: ${useOverridesMode}`, () => { it(`no-op when $anon_distinct_id not passed`, async () => { - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: newUserDistinctId, properties: { @@ -754,6 +777,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual(undefined) const persons = await fetchPostgresPersonsH() @@ -761,7 +785,7 @@ describe('PersonState.update()', () => { }) it(`creates person with both distinct_ids and marks user as is_identified when $anon_distinct_id passed`, async () => { - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: newUserDistinctId, properties: { @@ -770,6 +794,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -807,8 +832,9 @@ describe('PersonState.update()', () => { $anon_distinct_id: oldUserDistinctId, }, }) - const person = await personS.handleIdentifyOrAlias() + const [person, kafkaAcks] = await personS.handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -838,8 +864,9 @@ describe('PersonState.update()', () => { $anon_distinct_id: oldUserDistinctId, }, }) - const person = await personS.handleIdentifyOrAlias() + const [person, kafkaAcks] = await personS.handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks const persons = await fetchPostgresPersonsH() expect(person).toEqual( @@ -873,8 +900,9 @@ describe('PersonState.update()', () => { $anon_distinct_id: oldUserDistinctId, }, }) - const person = await personS.handleIdentifyOrAlias() + const [person, kafkaAcks] = await personS.handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks const persons = await fetchPostgresPersonsH() @@ -903,7 +931,7 @@ describe('PersonState.update()', () => { await hub.db.createPerson(timestamp, {}, {}, {}, teamId, null, false, oldUserUuid, [oldUserDistinctId]) await hub.db.createPerson(timestamp2, {}, {}, {}, teamId, null, false, newUserUuid, [newUserDistinctId]) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: newUserDistinctId, properties: { @@ -911,6 +939,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -965,7 +994,7 @@ describe('PersonState.update()', () => { await hub.db.createPerson(timestamp, {}, {}, {}, teamId, null, false, oldUserUuid, [oldUserDistinctId]) await hub.db.createPerson(timestamp2, {}, {}, {}, teamId, null, true, newUserUuid, [newUserDistinctId]) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: newUserDistinctId, properties: { @@ -973,6 +1002,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -1034,8 +1064,9 @@ describe('PersonState.update()', () => { $anon_distinct_id: oldUserDistinctId, }, }) - const person = await personS.handleIdentifyOrAlias() + const [person, kafkaAcks] = await personS.handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(personS.updateIsIdentified).toBeTruthy() expect(person).toEqual( @@ -1075,7 +1106,7 @@ describe('PersonState.update()', () => { await hub.db.createPerson(timestamp, {}, {}, {}, teamId, null, true, oldUserUuid, [oldUserDistinctId]) await hub.db.createPerson(timestamp2, {}, {}, {}, teamId, null, true, newUserUuid, [newUserDistinctId]) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: newUserDistinctId, properties: { @@ -1083,6 +1114,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -1125,7 +1157,7 @@ describe('PersonState.update()', () => { newUserDistinctId, ]) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: newUserDistinctId, properties: { @@ -1135,6 +1167,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -1204,7 +1237,7 @@ describe('PersonState.update()', () => { await hub.db.addDistinctId(person, distinctId, 0) // this throws }) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$identify', distinct_id: oldUserDistinctId, properties: { @@ -1212,6 +1245,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks jest.spyOn(hub.db, 'addDistinctId').mockRestore() // Necessary for other tests not to fail // if creation fails we should return the person that another thread already created @@ -1250,7 +1284,7 @@ describe('PersonState.update()', () => { hub ) jest.spyOn(state, 'merge').mockImplementation(() => { - return Promise.resolve(undefined) + return Promise.resolve([undefined, Promise.resolve()]) }) await state.handleIdentifyOrAlias() expect(state.merge).toHaveBeenCalledWith(oldUserDistinctId, newUserDistinctId, teamId, timestamp) @@ -1267,7 +1301,7 @@ describe('PersonState.update()', () => { hub ) jest.spyOn(state, 'merge').mockImplementation(() => { - return Promise.resolve(undefined) + return Promise.resolve([undefined, Promise.resolve()]) }) await state.handleIdentifyOrAlias() @@ -1285,7 +1319,7 @@ describe('PersonState.update()', () => { hub ) jest.spyOn(state, 'merge').mockImplementation(() => { - return Promise.resolve(undefined) + return Promise.resolve([undefined, Promise.resolve()]) }) await state.handleIdentifyOrAlias() @@ -1305,7 +1339,7 @@ describe('PersonState.update()', () => { await hub.db.createPerson(timestamp, {}, {}, {}, teamId, null, true, oldUserUuid, [oldUserDistinctId]) await hub.db.createPerson(timestamp2, {}, {}, {}, teamId, null, true, newUserUuid, [newUserDistinctId]) - const person = await personState({ + const [person, kafkaAcks] = await personState({ event: '$merge_dangerously', distinct_id: newUserDistinctId, properties: { @@ -1313,6 +1347,7 @@ describe('PersonState.update()', () => { }, }).handleIdentifyOrAlias() await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -1368,7 +1403,7 @@ describe('PersonState.update()', () => { describe('illegal aliasing', () => { const illegalIds = ['', ' ', 'null', 'undefined', '"undefined"', '[object Object]', '"[object Object]"'] it.each(illegalIds)('stops $identify if current distinct_id is illegal: `%s`', async (illegalId: string) => { - const person = await personState({ + const [person] = await personState({ event: '$identify', distinct_id: illegalId, properties: { @@ -1382,7 +1417,7 @@ describe('PersonState.update()', () => { }) it.each(illegalIds)('stops $identify if $anon_distinct_id is illegal: `%s`', async (illegalId: string) => { - const person = await personState({ + const [person] = await personState({ event: '$identify', distinct_id: 'some_distinct_id', properties: { @@ -1396,7 +1431,7 @@ describe('PersonState.update()', () => { }) it('stops $create_alias if current distinct_id is illegal', async () => { - const person = await personState({ + const [person] = await personState({ event: '$create_alias', distinct_id: 'false', properties: { @@ -1410,7 +1445,7 @@ describe('PersonState.update()', () => { }) it('stops $create_alias if alias is illegal', async () => { - const person = await personState({ + const [person] = await personState({ event: '$create_alias', distinct_id: 'some_distinct_id', properties: { @@ -1685,8 +1720,14 @@ describe('PersonState.update()', () => { ]) const state: PersonState = personState({}, hub) jest.spyOn(hub.db.kafkaProducer, 'queueMessages') - const person = await state.merge(secondUserDistinctId, firstUserDistinctId, teamId, timestamp) + const [person, kafkaAcks] = await state.merge( + secondUserDistinctId, + firstUserDistinctId, + teamId, + timestamp + ) await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -1728,13 +1769,14 @@ describe('PersonState.update()', () => { const state: PersonState = personState({}, hub) jest.spyOn(hub.db.kafkaProducer, 'queueMessages') - const person = await state.mergePeople({ + const [person, kafkaAcks] = await state.mergePeople({ mergeInto: first, mergeIntoDistinctId: firstUserDistinctId, otherPerson: second, otherPersonDistinctId: secondUserDistinctId, }) await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ @@ -2060,13 +2102,14 @@ describe('PersonState.update()', () => { // Now verify we successfully get to our target state if we do not have // any db errors. mockPostgresQuery.mockRestore() - const person = await state.mergePeople({ + const [person, kafkaAcks] = await state.mergePeople({ mergeInto: first, mergeIntoDistinctId: firstUserDistinctId, otherPerson: second, otherPersonDistinctId: secondUserDistinctId, }) await hub.db.kafkaProducer.flush() + await kafkaAcks expect(person).toEqual( expect.objectContaining({ diff --git a/plugin-server/tests/worker/ingestion/postgres-parity.test.ts b/plugin-server/tests/worker/ingestion/postgres-parity.test.ts index 142b7c6938bd66..5b12393a63b10b 100644 --- a/plugin-server/tests/worker/ingestion/postgres-parity.test.ts +++ b/plugin-server/tests/worker/ingestion/postgres-parity.test.ts @@ -170,11 +170,16 @@ describe('postgres parity', () => { await delayUntilEventIngested(() => hub.db.fetchDistinctIdValues(person, Database.ClickHouse), 2) // update properties and set is_identified to true - await hub.db.updatePersonDeprecated(person, { + const [_p, kafkaMessages] = await hub.db.updatePersonDeprecated(person, { properties: { replacedUserProp: 'propValue' }, is_identified: true, }) + await hub.db.kafkaProducer.queueMessages({ + kafkaMessages, + waitForAck: true, + }) + await delayUntilEventIngested(async () => (await hub.db.fetchPersons(Database.ClickHouse)).filter((p) => p.is_identified) ) @@ -196,11 +201,16 @@ describe('postgres parity', () => { // update date and boolean to false const randomDate = DateTime.utc().minus(100000).setZone('UTC') - const [updatedPerson] = await hub.db.updatePersonDeprecated(person, { + const [updatedPerson, kafkaMessages2] = await hub.db.updatePersonDeprecated(person, { created_at: randomDate, is_identified: false, }) + await hub.db.kafkaProducer.queueMessages({ + kafkaMessages: kafkaMessages2, + waitForAck: true, + }) + expect(updatedPerson.version).toEqual(2) await delayUntilEventIngested(async () => From b5f7926a885cb18aa2799a29664346e95df6866a Mon Sep 17 00:00:00 2001 From: Tom Owers Date: Mon, 10 Jun 2024 16:42:17 +0100 Subject: [PATCH 13/35] Fix table nesting (#22852) --- posthog/temporal/data_imports/pipelines/stripe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/posthog/temporal/data_imports/pipelines/stripe/__init__.py b/posthog/temporal/data_imports/pipelines/stripe/__init__.py index 2fe2df90ce0e18..228e94778e6890 100644 --- a/posthog/temporal/data_imports/pipelines/stripe/__init__.py +++ b/posthog/temporal/data_imports/pipelines/stripe/__init__.py @@ -200,7 +200,7 @@ def update_request(self, request: Request) -> None: request.params["starting_after"] = self._starting_after -@dlt.source +@dlt.source(max_table_nesting=0) def stripe_source(api_key: str, account_id: str, endpoint: str, is_incremental: bool = False): config: RESTAPIConfig = { "client": { From d868ee9c819b7352c1dc5e097d7542c47310d84b Mon Sep 17 00:00:00 2001 From: PostHog Bot <69588470+posthog-bot@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:05:06 -0400 Subject: [PATCH 14/35] chore(deps): Update posthog-js to 1.138.3 (#22836) --- package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f075f3dabba469..8080ba5fb67f19 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,7 @@ "pmtiles": "^2.11.0", "postcss": "^8.4.31", "postcss-preset-env": "^9.3.0", - "posthog-js": "1.138.2", + "posthog-js": "1.138.3", "posthog-js-lite": "3.0.0", "prettier": "^2.8.8", "prop-types": "^15.7.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12e300d9cae7b2..aed588d82bacdb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -260,8 +260,8 @@ dependencies: specifier: ^9.3.0 version: 9.3.0(postcss@8.4.31) posthog-js: - specifier: 1.138.2 - version: 1.138.2 + specifier: 1.138.3 + version: 1.138.3 posthog-js-lite: specifier: 3.0.0 version: 3.0.0 @@ -17707,8 +17707,8 @@ packages: resolution: {integrity: sha512-dyajjnfzZD1tht4N7p7iwf7nBnR1MjVaVu+MKr+7gBgA39bn28wizCIJZztZPtHy4PY0YwtSGgwfBCuG/hnHgA==} dev: false - /posthog-js@1.138.2: - resolution: {integrity: sha512-siin1JCAe8UIrc39qV5SFwxBcUB7zp80KNKp175McMGh3Vtw056AccFTBw6xpuIjX5hh23gfw7Pnr/VnI7MSfw==} + /posthog-js@1.138.3: + resolution: {integrity: sha512-egQFMHI7BqKMSaZ3NNaknNoNHfol2zUHPxQ0jnLsmS/mICCR03iOmX0CoW+5Enrw0fFXXKd0oACq5t1+9SmL2Q==} dependencies: fflate: 0.4.8 preact: 10.22.0 From 197e64432063966f594546a75f1dc57c1618e4d8 Mon Sep 17 00:00:00 2001 From: Brett Hoerner Date: Mon, 10 Jun 2024 10:07:05 -0600 Subject: [PATCH 15/35] chore(plugin-server): drop non-lazy personless mode (#22774) --- plugin-server/src/config/config.ts | 1 - plugin-server/src/types.ts | 2 - plugin-server/src/utils/db/hub.ts | 1 - .../event-pipeline/processPersonsStep.ts | 1 - .../src/worker/ingestion/person-state.ts | 76 ++++++++----------- .../worker/ingestion/person-state.test.ts | 61 ++------------- 6 files changed, 36 insertions(+), 106 deletions(-) diff --git a/plugin-server/src/config/config.ts b/plugin-server/src/config/config.ts index f3584ded83880d..3ab2cb79a536d9 100644 --- a/plugin-server/src/config/config.ts +++ b/plugin-server/src/config/config.ts @@ -137,7 +137,6 @@ export function getDefaultConfig(): PluginsServerConfig { RUSTY_HOOK_ROLLOUT_PERCENTAGE: 0, RUSTY_HOOK_URL: '', CAPTURE_CONFIG_REDIS_HOST: null, - LAZY_PERSON_CREATION_TEAMS: '', STARTUP_PROFILE_DURATION_SECONDS: 300, // 5 minutes STARTUP_PROFILE_CPU: false, diff --git a/plugin-server/src/types.ts b/plugin-server/src/types.ts index 8b3c74328afba6..78996b2a4fad97 100644 --- a/plugin-server/src/types.ts +++ b/plugin-server/src/types.ts @@ -212,7 +212,6 @@ export interface PluginsServerConfig { SKIP_UPDATE_EVENT_AND_PROPERTIES_STEP: boolean PIPELINE_STEP_STALLED_LOG_TIMEOUT: number CAPTURE_CONFIG_REDIS_HOST: string | null // Redis cluster to use to coordinate with capture (overflow, routing) - LAZY_PERSON_CREATION_TEAMS: string // dump profiles to disk, covering the first N seconds of runtime STARTUP_PROFILE_DURATION_SECONDS: number @@ -299,7 +298,6 @@ export interface Hub extends PluginsServerConfig { pluginConfigsToSkipElementsParsing: ValueMatcher poeEmbraceJoinForTeams: ValueMatcher poeWritesExcludeTeams: ValueMatcher - lazyPersonCreationTeams: ValueMatcher // lookups eventsToDropByToken: Map } diff --git a/plugin-server/src/utils/db/hub.ts b/plugin-server/src/utils/db/hub.ts index 359f1f9f7996aa..3feaf4cd63c63d 100644 --- a/plugin-server/src/utils/db/hub.ts +++ b/plugin-server/src/utils/db/hub.ts @@ -204,7 +204,6 @@ export async function createHub( pluginConfigsToSkipElementsParsing: buildIntegerMatcher(process.env.SKIP_ELEMENTS_PARSING_PLUGINS, true), poeEmbraceJoinForTeams: buildIntegerMatcher(process.env.POE_EMBRACE_JOIN_FOR_TEAMS, true), poeWritesExcludeTeams: buildIntegerMatcher(process.env.POE_WRITES_EXCLUDE_TEAMS, false), - lazyPersonCreationTeams: buildIntegerMatcher(process.env.LAZY_PERSON_CREATION_TEAMS, true), eventsToDropByToken: createEventsToDropByToken(process.env.DROP_EVENTS_BY_TOKEN_DISTINCT_ID), } diff --git a/plugin-server/src/worker/ingestion/event-pipeline/processPersonsStep.ts b/plugin-server/src/worker/ingestion/event-pipeline/processPersonsStep.ts index 7f2e359e2e0173..377981fe64b095 100644 --- a/plugin-server/src/worker/ingestion/event-pipeline/processPersonsStep.ts +++ b/plugin-server/src/worker/ingestion/event-pipeline/processPersonsStep.ts @@ -23,7 +23,6 @@ export async function processPersonsStep( timestamp, processPerson, runner.hub.db, - runner.hub.lazyPersonCreationTeams(event.team_id), overridesWriter ).update() diff --git a/plugin-server/src/worker/ingestion/person-state.ts b/plugin-server/src/worker/ingestion/person-state.ts index 60202bf2a5900c..d3bf32e21310b5 100644 --- a/plugin-server/src/worker/ingestion/person-state.ts +++ b/plugin-server/src/worker/ingestion/person-state.ts @@ -92,7 +92,6 @@ export class PersonState { private timestamp: DateTime, private processPerson: boolean, // $process_person_profile flag from the event private db: DB, - private lazyPersonCreation: boolean, private personOverrideWriter?: DeferredPersonOverrideWriter ) { this.eventProperties = event.properties! @@ -104,50 +103,39 @@ export class PersonState { async update(): Promise<[Person, Promise]> { if (!this.processPerson) { - if (this.lazyPersonCreation) { - const existingPerson = await this.db.fetchPerson(this.teamId, this.distinctId, { useReadReplica: true }) - if (existingPerson) { - const person = existingPerson as Person - - // Ensure person properties don't propagate elsewhere, such as onto the event itself. - person.properties = {} - - if (this.timestamp > person.created_at.plus({ minutes: 1 })) { - // See documentation on the field. - // - // Note that we account for timestamp vs person creation time (with a little - // padding for good measure) to account for ingestion lag. It's possible for - // events to be processed after person creation even if they were sent prior - // to person creation, and the user did nothing wrong in that case. - person.force_upgrade = true - } - - return [person, Promise.resolve()] - } - - // We need a value from the `person_created_column` in ClickHouse. This should be - // hidden from users for events without a real person, anyway. It's slightly offset - // from the 0 date (by 5 seconds) in order to assist in debugging by being - // harmlessly distinct from Unix UTC "0". - const createdAt = DateTime.utc(1970, 1, 1, 0, 0, 5) - - const fakePerson: Person = { - team_id: this.teamId, - properties: {}, - uuid: uuidFromDistinctId(this.teamId, this.distinctId), - created_at: createdAt, - } - return [fakePerson, Promise.resolve()] - } else { - // We don't need to handle any properties for `processPerson=false` events, so we can - // short circuit by just finding or creating a person and returning early. - const [person, _] = await promiseRetry(() => this.createOrGetPerson(), 'get_person_personless') + const existingPerson = await this.db.fetchPerson(this.teamId, this.distinctId, { useReadReplica: true }) + if (existingPerson) { + const person = existingPerson as Person // Ensure person properties don't propagate elsewhere, such as onto the event itself. person.properties = {} + if (this.timestamp > person.created_at.plus({ minutes: 1 })) { + // See documentation on the field. + // + // Note that we account for timestamp vs person creation time (with a little + // padding for good measure) to account for ingestion lag. It's possible for + // events to be processed after person creation even if they were sent prior + // to person creation, and the user did nothing wrong in that case. + person.force_upgrade = true + } + return [person, Promise.resolve()] } + + // We need a value from the `person_created_column` in ClickHouse. This should be + // hidden from users for events without a real person, anyway. It's slightly offset + // from the 0 date (by 5 seconds) in order to assist in debugging by being + // harmlessly distinct from Unix UTC "0". + const createdAt = DateTime.utc(1970, 1, 1, 0, 0, 5) + + const fakePerson: Person = { + team_id: this.teamId, + properties: {}, + uuid: uuidFromDistinctId(this.teamId, this.distinctId), + created_at: createdAt, + } + return [fakePerson, Promise.resolve()] } const [person, identifyOrAliasKafkaAck]: [InternalPerson | undefined, Promise] = @@ -419,8 +407,9 @@ export class PersonState { // Overrides are only created when the version is > 0, see: // https://github.com/PostHog/posthog/blob/92e17ce307a577c4233d4ab252eebc6c2207a5ee/posthog/models/person/sql.py#L269-L287 // - // With the addition of optional person processing, we are now rolling out a change to - // lazily create `posthog_persondistinctid` and `posthog_person` rows. This means that: + // With the addition of optional person processing, we are no longer creating + // `posthog_persondistinctid` and `posthog_person` rows when $process_person_profile=false. + // This means that: // 1. At merge time, it's possible this `distinct_id` and its deterministically generated // `person.uuid` has already been used for events in ClickHouse, but they have no // corresponding rows in the `posthog_persondistinctid` or `posthog_person` tables @@ -429,10 +418,7 @@ export class PersonState { // `distinct_id` even though we're just now INSERT-ing it into Postgres/ClickHouse. We do // this by starting with `version=1`, as if we had just deleted the old user and were // updating the `distinct_id` row as part of the merge - let addDistinctIdVersion = 0 - if (this.lazyPersonCreation) { - addDistinctIdVersion = 1 - } + const addDistinctIdVersion = 1 if (otherPerson && !mergeIntoPerson) { await this.db.addDistinctId(otherPerson, mergeIntoDistinctId, addDistinctIdVersion) diff --git a/plugin-server/tests/worker/ingestion/person-state.test.ts b/plugin-server/tests/worker/ingestion/person-state.test.ts index 0c363f4f75d610..ab921d71902cc1 100644 --- a/plugin-server/tests/worker/ingestion/person-state.test.ts +++ b/plugin-server/tests/worker/ingestion/person-state.test.ts @@ -111,7 +111,6 @@ describe('PersonState.update()', () => { event: Partial, customHub?: Hub, processPerson = true, - lazyPersonCreation = false, timestampParam = timestamp ) { const fullEvent = { @@ -127,7 +126,6 @@ describe('PersonState.update()', () => { timestampParam, processPerson, customHub ? customHub.db : hub.db, - lazyPersonCreation, overridesMode?.getWriter(customHub ?? hub) ) } @@ -187,12 +185,11 @@ describe('PersonState.update()', () => { expect(personPrimaryTeam.uuid).not.toEqual(personOtherTeam.uuid) }) - it('returns an ephemeral user object when lazy creation is enabled and $process_person_profile=false', async () => { + it('returns an ephemeral user object when $process_person_profile=false', async () => { const event_uuid = new UUIDT().toString() const hubParam = undefined const processPerson = false - const lazyPersonCreation = true const [fakePerson, kafkaAcks] = await personState( { event: '$pageview', @@ -201,8 +198,7 @@ describe('PersonState.update()', () => { properties: { $set: { should_be_dropped: 100 } }, }, hubParam, - processPerson, - lazyPersonCreation + processPerson ).update() await hub.db.kafkaProducer.flush() await kafkaAcks @@ -226,12 +222,11 @@ describe('PersonState.update()', () => { expect(distinctIds).toEqual(expect.arrayContaining([])) }) - it('merging with lazy person creation creates an override and force_upgrade works', async () => { + it('merging creates an override and force_upgrade works', async () => { await hub.db.createPerson(timestamp, {}, {}, {}, teamId, null, false, oldUserUuid, [oldUserDistinctId]) const hubParam = undefined let processPerson = true - const lazyPersonCreation = true const [_person, kafkaAcks] = await personState( { event: '$identify', @@ -241,8 +236,7 @@ describe('PersonState.update()', () => { }, }, hubParam, - processPerson, - lazyPersonCreation + processPerson ).update() await hub.db.kafkaProducer.flush() await kafkaAcks @@ -276,7 +270,6 @@ describe('PersonState.update()', () => { }, hubParam, processPerson, - lazyPersonCreation, timestampParam ).update() await hub.db.kafkaProducer.flush() @@ -329,50 +322,6 @@ describe('PersonState.update()', () => { expect(distinctIds).toEqual(expect.arrayContaining([newUserDistinctId])) }) - it('creates person if they are new and $process_person_profile=false', async () => { - // Note that eventually $process_person_profile=false will be optimized so that the person is - // *not* created here. - const event_uuid = new UUIDT().toString() - const processPerson = false - const [person, kafkaAcks] = await personState( - { - event: '$pageview', - distinct_id: newUserDistinctId, - uuid: event_uuid, - properties: { $process_person_profile: false, $set: { a: 1 }, $set_once: { b: 2 } }, - }, - hub, - processPerson - ).update() - await hub.db.kafkaProducer.flush() - await kafkaAcks - - expect(person).toEqual( - expect.objectContaining({ - id: expect.any(Number), - uuid: newUserUuid, - properties: {}, - created_at: timestamp, - version: 0, - is_identified: false, - }) - ) - - expect(hub.db.fetchPerson).toHaveBeenCalledTimes(1) - expect(hub.db.updatePersonDeprecated).not.toHaveBeenCalled() - - // verify Postgres persons - const persons = await fetchPostgresPersonsH() - expect(persons.length).toEqual(1) - // For parity with existing functionality, the Person created in the DB actually gets - // the $creator_event_uuid property. When we stop creating person rows this won't matter. - expect(persons[0]).toEqual({ ...person, properties: { $creator_event_uuid: event_uuid } }) - - // verify Postgres distinct_ids - const distinctIds = await hub.db.fetchDistinctIdValues(persons[0]) - expect(distinctIds).toEqual(expect.arrayContaining([newUserDistinctId])) - }) - it('does not attach existing person properties to $process_person_profile=false events', async () => { const originalEventUuid = new UUIDT().toString() const [person, kafkaAcks] = await personState({ @@ -802,7 +751,7 @@ describe('PersonState.update()', () => { uuid: newUserUuid, properties: { foo: 'bar' }, created_at: timestamp, - version: 0, + version: 1, is_identified: true, }) ) From 81a080ea43afbc8151559d8f4536e8790b044d9e Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 10 Jun 2024 17:14:09 +0100 Subject: [PATCH 16/35] fix: Always reload hog function on change (#22853) --- .../src/cdp/cdp-processed-events-consumer.ts | 13 +++++-------- plugin-server/src/cdp/hog-function-manager.ts | 8 ++------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/plugin-server/src/cdp/cdp-processed-events-consumer.ts b/plugin-server/src/cdp/cdp-processed-events-consumer.ts index 300b592334686c..5004bb9655bcee 100644 --- a/plugin-server/src/cdp/cdp-processed-events-consumer.ts +++ b/plugin-server/src/cdp/cdp-processed-events-consumer.ts @@ -26,7 +26,6 @@ require('@sentry/tracing') // WARNING: Do not change this - it will essentially reset the consumer const KAFKA_CONSUMER_GROUP_ID = 'cdp-function-executor' -const KAFKA_CONSUMER_SESSION_TIMEOUT_MS = 90_000 const BUCKETS_KB_WRITTEN = [0, 128, 512, 1024, 5120, 10240, 20480, 51200, 102400, 204800, Infinity] const histogramKafkaBatchSize = new Histogram({ @@ -152,15 +151,13 @@ export class CdpProcessedEventsConsumer { await runInstrumentedFunction({ statsKey: `cdpFunctionExecutor.handleEachBatch.consumeBatch`, func: async () => { - // TODO: Parallelise this - for (const message of invocations) { - const results = await this.consume(message) - invocationResults.push(...results) - heartbeat() - } + const results = await Promise.all(invocations.map((invocation) => this.consume(invocation))) + invocationResults.push(...results.flat()) }, }) + heartbeat() + // TODO: Follow up - process metrics from the invocationResults // await runInstrumentedFunction({ // statsKey: `cdpFunctionExecutor.handleEachBatch.queueMetrics`, @@ -204,7 +201,7 @@ export class CdpProcessedEventsConsumer { groupId: this.consumerGroupId, topic: this.topic, autoCommit: true, - sessionTimeout: KAFKA_CONSUMER_SESSION_TIMEOUT_MS, + sessionTimeout: this.config.KAFKA_CONSUMPTION_SESSION_TIMEOUT_MS, maxPollIntervalMs: this.config.KAFKA_CONSUMPTION_MAX_POLL_INTERVAL_MS, // the largest size of a message that can be fetched by the consumer. // the largest size our MSK cluster allows is 20MB diff --git a/plugin-server/src/cdp/hog-function-manager.ts b/plugin-server/src/cdp/hog-function-manager.ts index 53a1dc5da90a3d..853d6ff951985b 100644 --- a/plugin-server/src/cdp/hog-function-manager.ts +++ b/plugin-server/src/cdp/hog-function-manager.ts @@ -24,11 +24,7 @@ export class HogFunctionManager { this.pubSub = new PubSub(this.serverConfig, { 'reload-hog-function': async (message) => { const { hogFunctionId, teamId } = JSON.parse(message) - // TODO: Change to only if already loaded - - if (this.cache[teamId]?.[hogFunctionId]) { - await this.reloadHogFunction(teamId, hogFunctionId) - } + await this.reloadHogFunction(teamId, hogFunctionId) }, }) } @@ -72,8 +68,8 @@ export class HogFunctionManager { } public async reloadHogFunction(teamId: Team['id'], id: HogFunctionType['id']): Promise { + status.info('🍿', `Reloading hog function ${id} from DB`) const item = await fetchHogFunction(this.postgres, id) - if (item) { this.cache[teamId][id] = item } else { From d0331e09d329f9abfaf36fc7d135be83e53d17d0 Mon Sep 17 00:00:00 2001 From: Michael Matloka Date: Mon, 10 Jun 2024 18:32:13 +0200 Subject: [PATCH 17/35] fix(activity): Properly scope live events (#22856) --- posthog/api/team.py | 4 ++-- posthog/jwt.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/posthog/api/team.py b/posthog/api/team.py index bb3395a5c56ea0..e96ab0820eb55d 100644 --- a/posthog/api/team.py +++ b/posthog/api/team.py @@ -199,9 +199,9 @@ def get_groups_on_events_querying_enabled(self, team: Team) -> bool: def get_live_events_token(self, team: Team) -> Optional[str]: return encode_jwt( - {"team_id": 2}, + {"team_id": team.id}, timedelta(days=7), - PosthogJwtAudience.LIVE_EVENTS, + PosthogJwtAudience.LIVESTREAM, ) def validate_session_recording_linked_flag(self, value) -> dict | None: diff --git a/posthog/jwt.py b/posthog/jwt.py index 62710d9159b862..ead4196aa47301 100644 --- a/posthog/jwt.py +++ b/posthog/jwt.py @@ -10,7 +10,7 @@ class PosthogJwtAudience(Enum): UNSUBSCRIBE = "posthog:unsubscribe" EXPORTED_ASSET = "posthog:exported_asset" IMPERSONATED_USER = "posthog:impersonted_user" # This is used by background jobs on behalf of the user e.g. exports - LIVE_EVENTS = "posthog:live_events" + LIVESTREAM = "posthog:livestream" def encode_jwt(payload: dict, expiry_delta: timedelta, audience: PosthogJwtAudience) -> str: From 39a429d4c4e4e786b912a9fd0f2d8ee01a932e43 Mon Sep 17 00:00:00 2001 From: Ben White Date: Mon, 10 Jun 2024 17:58:47 +0100 Subject: [PATCH 18/35] fix: Redirect params for login buttons (#22841) --- .../SocialLoginButton/SocialLoginButton.tsx | 25 +++++++++++-------- .../TimeSensitiveAuthentication.tsx | 22 ++++++++++++---- .../scenes/authentication/InviteSignup.tsx | 2 +- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/frontend/src/lib/components/SocialLoginButton/SocialLoginButton.tsx b/frontend/src/lib/components/SocialLoginButton/SocialLoginButton.tsx index ea54e1ddfa2f75..6edef668ef4322 100644 --- a/frontend/src/lib/components/SocialLoginButton/SocialLoginButton.tsx +++ b/frontend/src/lib/components/SocialLoginButton/SocialLoginButton.tsx @@ -39,10 +39,10 @@ function SocialLoginLink({ provider, extraQueryParams, children }: SocialLoginLi interface SocialLoginButtonProps { provider: SSOProvider - redirectQueryParams?: Record + extraQueryParams?: Record } -export function SocialLoginButton({ provider, redirectQueryParams }: SocialLoginButtonProps): JSX.Element | null { +export function SocialLoginButton({ provider, extraQueryParams }: SocialLoginButtonProps): JSX.Element | null { const { preflight } = useValues(preflightLogic) if (!preflight?.available_social_auth_providers[provider]) { @@ -50,7 +50,7 @@ export function SocialLoginButton({ provider, redirectQueryParams }: SocialLogin } return ( - + }> {SSO_PROVIDER_NAMES[provider]} @@ -65,7 +65,7 @@ interface SocialLoginButtonsProps { className?: string topDivider?: boolean bottomDivider?: boolean - redirectQueryParams?: Record + extraQueryParams?: Record } export function SocialLoginButtons({ @@ -109,14 +109,19 @@ export function SocialLoginButtons({ ) } -interface SSOEnforcedLoginButtonProps extends Partial { - provider: SSOProvider - email: string -} +type SSOEnforcedLoginButtonProps = SocialLoginButtonProps & + Partial & { + email: string + } -export function SSOEnforcedLoginButton({ provider, email, ...props }: SSOEnforcedLoginButtonProps): JSX.Element { +export function SSOEnforcedLoginButton({ + provider, + email, + extraQueryParams, + ...props +}: SSOEnforcedLoginButtonProps): JSX.Element { return ( - + - + ) : showPassword ? ( {precheckResponse?.saml_available ? ( - + ) : null}
) : null} diff --git a/frontend/src/scenes/authentication/InviteSignup.tsx b/frontend/src/scenes/authentication/InviteSignup.tsx index af96ad730bd15e..32a612d36e93bb 100644 --- a/frontend/src/scenes/authentication/InviteSignup.tsx +++ b/frontend/src/scenes/authentication/InviteSignup.tsx @@ -285,7 +285,7 @@ function UnauthenticatedAcceptInvite({ invite }: { invite: PrevalidatedInvite }) caption={`Remember to log in with ${invite?.target_email}`} captionLocation="bottom" topDivider - redirectQueryParams={invite ? { invite_id: invite.id } : undefined} + extraQueryParams={invite ? { invite_id: invite.id } : undefined} /> ) From 9a2f08e50e2f7459def29794da893591331528a9 Mon Sep 17 00:00:00 2001 From: PostHog Bot <69588470+posthog-bot@users.noreply.github.com> Date: Mon, 10 Jun 2024 14:44:54 -0400 Subject: [PATCH 19/35] chore(deps): Update posthog-js to 1.139.0 (#22858) --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 8080ba5fb67f19..63f1cdcce36882 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,7 @@ "pmtiles": "^2.11.0", "postcss": "^8.4.31", "postcss-preset-env": "^9.3.0", - "posthog-js": "1.138.3", + "posthog-js": "1.139.0", "posthog-js-lite": "3.0.0", "prettier": "^2.8.8", "prop-types": "^15.7.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aed588d82bacdb..6c9eab81c8a898 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -260,8 +260,8 @@ dependencies: specifier: ^9.3.0 version: 9.3.0(postcss@8.4.31) posthog-js: - specifier: 1.138.3 - version: 1.138.3 + specifier: 1.139.0 + version: 1.139.0 posthog-js-lite: specifier: 3.0.0 version: 3.0.0 @@ -17707,8 +17707,8 @@ packages: resolution: {integrity: sha512-dyajjnfzZD1tht4N7p7iwf7nBnR1MjVaVu+MKr+7gBgA39bn28wizCIJZztZPtHy4PY0YwtSGgwfBCuG/hnHgA==} dev: false - /posthog-js@1.138.3: - resolution: {integrity: sha512-egQFMHI7BqKMSaZ3NNaknNoNHfol2zUHPxQ0jnLsmS/mICCR03iOmX0CoW+5Enrw0fFXXKd0oACq5t1+9SmL2Q==} + /posthog-js@1.139.0: + resolution: {integrity: sha512-FuYlxQFO0Dq5X1/bFEM8F+NgOqZiVh4fPVHHeOTWMkqVP+pCnODQitbtW0hgT0/EE665w0xpZBk93YavaZRhzQ==} dependencies: fflate: 0.4.8 preact: 10.22.0 @@ -22092,4 +22092,4 @@ packages: /zxcvbn@4.4.2: resolution: {integrity: sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ==} - dev: false \ No newline at end of file + dev: false From edd76ca31bb227da143104738c5497c1d0f1f2b6 Mon Sep 17 00:00:00 2001 From: Michael Matloka Date: Mon, 10 Jun 2024 21:13:30 +0200 Subject: [PATCH 20/35] chore(dev): Live events locally (#22837) * chore(dev): Live events locally * Fix JWT secret var * Also add Postgres URL * Update UI snapshots for `chromium` (2) --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- docker-compose.base.yml | 7 +++++++ docker-compose.dev.yml | 11 +++++++++++ docker/livestream/configs-dev.yml | 14 ++++++++++++++ ...-app-insights--trends-line-edit--light.png | Bin 145774 -> 133906 bytes frontend/src/lib/utils/apiHost.ts | 3 +-- 5 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 docker/livestream/configs-dev.yml diff --git a/docker-compose.base.yml b/docker-compose.base.yml index 2edb94dd9bc782..15adbe9d5febe7 100644 --- a/docker-compose.base.yml +++ b/docker-compose.base.yml @@ -115,6 +115,13 @@ services: CLICKHOUSE_SECURE: 'false' CLICKHOUSE_VERIFY: 'false' + livestream: + image: 'ghcr.io/posthog/livestream:main' + restart: on-failure + depends_on: + kafka: + condition: service_started + migrate: <<: *worker command: sh -c " diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index e45e3c3997ea9b..f79697c73fbfdb 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -117,6 +117,17 @@ services: - redis - kafka + livestream: + extends: + file: docker-compose.base.yml + service: livestream + environment: + - JWT.TOKEN=${SECRET_KEY} + ports: + - '8666:8080' + volumes: + - ./docker/livestream/configs-dev.yml:/configs/configs.yml + # Temporal containers elasticsearch: extends: diff --git a/docker/livestream/configs-dev.yml b/docker/livestream/configs-dev.yml new file mode 100644 index 00000000000000..5f7daf3ac52870 --- /dev/null +++ b/docker/livestream/configs-dev.yml @@ -0,0 +1,14 @@ +prod: false +tailscale: + controlUrl: + hostname: 'live-events-dev' +kafka: + brokers: 'kafka:9092' + topic: 'events_plugin_ingestion' + group_id: 'livestream-dev' +mmdb: + path: 'GeoLite2-City.mmdb' +jwt: + token: '' +postgres: + url: 'postgres://posthog:posthog@db:5432/posthog' diff --git a/frontend/__snapshots__/scenes-app-insights--trends-line-edit--light.png b/frontend/__snapshots__/scenes-app-insights--trends-line-edit--light.png index fb9a44bbb9c0919b20de8046088e5a3084b6b75f..0510a04db24d3c4796ebbed9ecbcb5fea5b320d7 100644 GIT binary patch delta 100962 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

X1!S0xt7`SP7iEYD*3h&-c zLjc(hdPg~lT4Dz&=p=}i_YS;tn^=V`SBI#|&8PPT+* zaClE^ya>2uT4-7Z}x7y6Q7q$}qak;4uZFT!XvJFq{I^Y(sj$&J3b*`TVWP}6f=7Ix=T^gXbsWz>b@!Cmup89fmZN3ff>zTf7M^DcPuEnkXtO@bCfy{ z6iWk^vKXs^F(o1>u6;K8<{)|h<-u>Jgg23t*vSl&G(eG}C`g3oOj1ovFs5DhTglrB zqElzGe8^8+w4x^kd&;X-Sxa}xWw7GEYro;5{u-zK6EW0}cn*a5;K>z&zk2wE}wkYZ!^hGTNu5`&b z9xkp`Yt)YXq_?!b|FDhf@5QPg1r0c RAvj!WN9jk>51sw_KLBaa5zYVr delta 112924 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 diff --git a/frontend/src/lib/utils/apiHost.ts b/frontend/src/lib/utils/apiHost.ts index bece9637612848..75d18c2bf060f8 100644 --- a/frontend/src/lib/utils/apiHost.ts +++ b/frontend/src/lib/utils/apiHost.ts @@ -15,6 +15,5 @@ export function liveEventsHostOrigin(): string | null { } else if (appOrigin === 'https://eu.posthog.com') { return 'https://live.eu.posthog.com' } - // TODO(@zach): add dev and local env support - return null + return 'http://localhost:8666' } From 5c159db08e453f5e22f7c406ca3628b574d30584 Mon Sep 17 00:00:00 2001 From: Michael Matloka Date: Mon, 10 Jun 2024 21:45:01 +0200 Subject: [PATCH 21/35] fix(activity): Use `beforeUnmount` to stop live events polling (#22724) * fix(activity): Use `beforeUnmount` to stop live events polling * Make active users wording juicier * Fix EventSource closing --- .../scenes/activity/live/LiveEventsTable.tsx | 2 +- .../activity/live/liveEventsTableLogic.tsx | 53 +++++++------------ 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/frontend/src/scenes/activity/live/LiveEventsTable.tsx b/frontend/src/scenes/activity/live/LiveEventsTable.tsx index 9ced6ffbc69a7a..a53ff7a1a1a5df 100644 --- a/frontend/src/scenes/activity/live/LiveEventsTable.tsx +++ b/frontend/src/scenes/activity/live/LiveEventsTable.tsx @@ -62,7 +62,7 @@ export function LiveEventsTable(): JSX.Element { - Active users: {stats?.users_on_product ?? '—'} + Users active right now: {stats?.users_on_product ?? '—'}

diff --git a/frontend/src/scenes/activity/live/liveEventsTableLogic.tsx b/frontend/src/scenes/activity/live/liveEventsTableLogic.tsx index efc76ece220f3c..a3fe1e96516355 100644 --- a/frontend/src/scenes/activity/live/liveEventsTableLogic.tsx +++ b/frontend/src/scenes/activity/live/liveEventsTableLogic.tsx @@ -18,7 +18,6 @@ export const liveEventsTableLogic = kea([ addEvents: (events) => ({ events }), clearEvents: true, setFilters: (filters) => ({ filters }), - updateEventsSource: (source) => ({ source }), updateEventsConnection: true, pauseStream: true, resumeStream: true, @@ -54,12 +53,6 @@ export const liveEventsTableLogic = kea([ setClientSideFilters: (_, { clientSideFilters }) => clientSideFilters, }, ], - eventsSource: [ - null as EventSource | null, - { - updateEventsSource: (_, { source }) => source, - }, - ], streamPaused: [ false, { @@ -110,8 +103,8 @@ export const liveEventsTableLogic = kea([ actions.updateEventsConnection() }, updateEventsConnection: async () => { - if (values.eventsSource) { - values.eventsSource.close() + if (cache.eventsSource) { + cache.eventsSource.close() } if (values.streamPaused) { @@ -124,14 +117,13 @@ export const liveEventsTableLogic = kea([ const { eventType } = values.filters const url = new URL(`${liveEventsHostOrigin()}/events`) - url.searchParams.append('teamId', values.currentTeam.id.toString()) if (eventType) { url.searchParams.append('eventType', eventType) } const source = new window.EventSourcePolyfill(url.toString(), { headers: { - Authorization: `Bearer ${values.currentTeam?.live_events_token}`, + Authorization: `Bearer ${values.currentTeam.live_events_token}`, }, }) @@ -158,11 +150,11 @@ export const liveEventsTableLogic = kea([ } } - actions.updateEventsSource(source) + cache.eventsSource = source }, pauseStream: () => { - if (values.eventsSource) { - values.eventsSource.close() + if (cache.eventsSource) { + cache.eventsSource.close() } }, resumeStream: () => { @@ -174,14 +166,11 @@ export const liveEventsTableLogic = kea([ return } - const response = await fetch( - `${liveEventsHostOrigin()}/stats?teamId=${values.currentTeam.id.toString()}`, - { - headers: { - Authorization: `Bearer ${values.currentTeam?.live_events_token}`, - }, - } - ) + const response = await fetch(`${liveEventsHostOrigin()}/stats`, { + headers: { + Authorization: `Bearer ${values.currentTeam.live_events_token}`, + }, + }) const data = await response.json() actions.setStats(data) } catch (error) { @@ -189,21 +178,19 @@ export const liveEventsTableLogic = kea([ } }, })), - events(({ actions, values }) => ({ + events(({ actions, cache }) => ({ afterMount: () => { - if (!liveEventsHostOrigin()) { - return - } - actions.updateEventsConnection() - const interval = setInterval(() => { + cache.statsInterval = setInterval(() => { actions.pollStats() }, 1500) - return () => { - if (values.eventsSource) { - values.eventsSource.close() - } - clearInterval(interval) + }, + beforeUnmount: () => { + if (cache.eventsSource) { + cache.eventsSource.close() + } + if (cache.statsInterval) { + clearInterval(cache.statsInterval) } }, })), From 76cdcfdf086ae8192d4be4036753d12e7d226b72 Mon Sep 17 00:00:00 2001 From: Raquel Smith Date: Mon, 10 Jun 2024 13:56:53 -0700 Subject: [PATCH 22/35] fix: typo in nuxt install instructions (#22818) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- .../scenes/onboarding/sdks/sdk-install-instructions/nuxt.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/scenes/onboarding/sdks/sdk-install-instructions/nuxt.tsx b/frontend/src/scenes/onboarding/sdks/sdk-install-instructions/nuxt.tsx index e57a46e534d610..baa4947ad49504 100644 --- a/frontend/src/scenes/onboarding/sdks/sdk-install-instructions/nuxt.tsx +++ b/frontend/src/scenes/onboarding/sdks/sdk-install-instructions/nuxt.tsx @@ -35,7 +35,7 @@ import posthog from 'posthog-js' export default defineNuxtPlugin(nuxtApp => { const runtimeConfig = useRuntimeConfig(); const posthogClient = posthog.init(runtimeConfig.public.posthogPublicKey, { - api_host: runtimeConfig.public.posthogHost', + api_host: runtimeConfig.public.posthogHost, ${ isPersonProfilesDisabled ? `` From 2b651fb570f0b1abe5fe323af53b88171c06b044 Mon Sep 17 00:00:00 2001 From: Raquel Smith Date: Mon, 10 Jun 2024 20:14:17 -0700 Subject: [PATCH 23/35] fix: update the person profiles changelog link (#22865) * update the person profiles changelog link * Update UI snapshots for `chromium` (2) --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- ...-app-insights--trends-line-edit--light.png | Bin 133906 -> 145774 bytes .../scenes/billing/BillingProductAddon.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/__snapshots__/scenes-app-insights--trends-line-edit--light.png b/frontend/__snapshots__/scenes-app-insights--trends-line-edit--light.png index 0510a04db24d3c4796ebbed9ecbcb5fea5b320d7..fb9a44bbb9c0919b20de8046088e5a3084b6b75f 100644 GIT binary patch delta 112924 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 delta 100962 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

X1!S0xt7`SP7iEYD*3h&-c zLjc(hdPg~lT4Dz&=p=}i_YS;tn^=V`SBI#|&8PPT+* zaClE^ya>2uT4-7Z}x7y6Q7q$}qak;4uZFT!XvJFq{I^Y(sj$&J3b*`TVWP}6f=7Ix=T^gXbsWz>b@!Cmup89fmZN3ff>zTf7M^DcPuEnkXtO@bCfy{ z6iWk^vKXs^F(o1>u6;K8<{)|h<-u>Jgg23t*vSl&G(eG}C`g3oOj1ovFs5DhTglrB zqElzGe8^8+w4x^kd&;X-Sxa}xWw7GEYro;5{u-zK6EW0}cn*a5;K>z&zk2wE}wkYZ!^hGTNu5`&b z9xkp`Yt)YXq_?!b|FDhf@5QPg1r0c RAvj!WN9jk>51sw_KLBaa5zYVr diff --git a/frontend/src/scenes/billing/BillingProductAddon.tsx b/frontend/src/scenes/billing/BillingProductAddon.tsx index b39fd32f0d51de..57a9c2edb2803a 100644 --- a/frontend/src/scenes/billing/BillingProductAddon.tsx +++ b/frontend/src/scenes/billing/BillingProductAddon.tsx @@ -97,7 +97,7 @@ export const BillingProductAddon = ({ addon }: { addon: BillingProductV2AddonTyp {is_enhanced_persons_og_customer && (

Date: Tue, 11 Jun 2024 09:06:10 +0100 Subject: [PATCH 24/35] feat: warn when heatmaps disabled (#22860) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- ...-app-insights--trends-line-edit--light.png | Bin 145774 -> 133906 bytes .../src/scenes/heatmaps/HeatmapsBrowser.tsx | 30 ++++++++++++++++-- frontend/src/scenes/scenes.ts | 1 - 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/frontend/__snapshots__/scenes-app-insights--trends-line-edit--light.png b/frontend/__snapshots__/scenes-app-insights--trends-line-edit--light.png index fb9a44bbb9c0919b20de8046088e5a3084b6b75f..0510a04db24d3c4796ebbed9ecbcb5fea5b320d7 100644 GIT binary patch delta 100962 zcmbTecRZH=|2KS!q+}&z&nTOc%*Yn9$;>Wn-=A!0sa^Ek_ z9NY$0C3UJo9gmz|!F-1CbUJOR9Ql!M)+XP4@SyF=NW9#G2W;D8Zu%`9 z9nv~F?>E1sn<6WU8d;hRA=G)RtNga3R6;M8JxfakJ~jSa&?6!udeWCoo}7|`OC|nt zf8A*R;NVAH9p+Mho=n{vyotFvKSr65ba!{DhK!KjEUh~D)`^LUbp7%>=0D&2;*xPj z4(^ck(e%u}y%8cfN7n0MZOwiT6Z2QDzL2D(WKDg2YBO>cBY#9e(o3&0w8xh>DR@u* zFwydNhIju~s8+VNmswl1(eeI9>(emGJJvM;#4OyV&4ic9d9jd_9kM$%!#Jq?{QR1d z=TrGF7F4tKN>x)nq(}}$hi|YheEa&_&8z8MyqfmWVY=SswzGrnth3z})YX>`vjg|? zPXA;Gtp9n0jPDD4(qQc6WgzPM#zE+WyFYW4yJEa!`N~CHTnTOM*v;pii6RpK(lhEP3&o;ENk?-@dimnpK>hp8lknqa!OP_hY2oDLgU~OIKHSU}%W+aFj5E zk&X^EI5;>^=E!y#504(lo40@f2dB&Nl_d{fp=US}e$n1^Fs#L%F2W3xP@I9sN75Ka zLc+v9NN#aMI<%4w2M6W>uHLJkU(1Y_yvSxGaCzAp*6C#d0RbF5JPp?Y7rEJiIVFA8 zo4B|^S9ab-HBE~$F-ewwqBpRe_|nmQT}@4G#?Hgm);22X#C9~EG(d8Gxbzl{@GH;x zdF09d(GgqQ&guxIq1%R3(z9%x!b_{GtJ>ep@#>9vTy_?H;jZbKn6Tj1w3ZG71J$UX z4SoMn>x+wl;?OPf`SOJ&gjxvu;lobe($nN2N`4!vipt6;{=uc*EI$c9{E4;dGtTXA z$BK*bhU!O4eK`_Sjll&jJC-QV&`<(S#GoSmcr;S$>6x_6NSUwrYwVj}_#NlF#7pyS zpN{oc1X?~Ad8Jb0s-suwoFL?c??*t}SmwB-{6&d+W&=4rxUg2aM>C3?^$zz&MqZuS zyN9}V$NHL1k#&Gv1alJ1aI51j|IwmhqaAbe_hZ&VW;fqO zFN~a0MMp=ou1u#XC5(=a4tzDkMU)Z*eUJ9mQcRjcO6(@ih0xv$X$qySYiW^7yWlcf z;XMDRm@9-z!290WF)=+o{rcWsQ?gXhrEEjisL48ldy=nS-90`&wu9;tjH&a7GS{i%WUH~Q$eNH(2kD3}(UteF%(&*`Ml7p>{)6VQ#DbmShSfzI`)HY+^ zlY3(Mu;H`fhsn*Ew)ueq#$1ESONNGHZUtl-e%kDBva;B&T)7gKkbtM9rInhRI^Gye zDJw7U{o#XdQl3CKvw3G?aC|(K8Sh}07FVU*vO_?_75|1SpL{-;Rm2VC=@iPx4a{xs z?a9c?Uoti^QAv|0@$o@?rpq%PjcRLakGDjyP<)Je|6V8a*=cKSzEK^w+rHh5$usuY z%#NQ=QR%wH^9QkHTn6=BNfJ>XORofAJ^NFnu{%(O2kW(U%wbN1%*o`-?S4ab*9J%F zt1<~&L6&E&QEaRT+pCd56>WRNtBwYYih@k?StUj2GV6lpyMvU?CFGhuevI*K=Mc%I50Lw>y{f@%@oAF05hP7>NSzU8ZtHS|AjM9$}HfL&#oxv7 zy`OaaTEJ;FULJ{y=Fq)Qi1poOMCTxM>EXST)ZY>&CMK^r^~=WLMqlkNlOJv`s7;UP zLdAq_d-?L^{8t;sJy}`=e3m_8a~%lO+{IeOyeiG?k>k|4)gD&c2Fno|B|82U}rEfHN`@q3okykpwBmp&f4bY^cpVN zhF+Cw5BGRM$4gM1t_i>5R#jECo2=6)ZEaJvU(_$)^x{Q5@+(EAz+q0wdbEPv!^4Bs zklD@M{rN=gd4)t_@(Tns_3d%D!o$Kosmh3np zgn{-rTH!xFTB~kNTJlx-qGV-d_5I1Ob5KfTnnI|(`Xv?(s@wvglxMUTlLbh|-hIv> z{q~yI2=qUb?$3&MU(6E<3tt-T|5aa)b?;<5!k(xGO8NNuL|wW@R&=Z1*=VKf^RrWT zz4DiMP>R0U@5~QVqG0LN!g|`+>b%F+Sx{K$8xcYLa&0u&b#HY7ssn4MR)*?%$ECgo zTIAU4?c2+#U%r_9DSB4&a^-#N!3BIO&o9Uq3ok?IK$yBwcYAx@QRg}8lCrW%bZJpk zwjt-dWHF>v&_w>3{JGJaSEEI#`hc-)thJRVNw>Qj6+r1OCLUb zs1?_zt}?wM?zT1i9g1*8MMY!~^0XFU8+3~=yh)KQru$2I)#{?f?d|Phu}EyXo7>K! zM&VRX^w+Y}3VR_&&0M_zn3cLl>2D-S4_vE0e4teND4Jux3{zp5khNeH86NIc=SNW2 z*ob}0qN6@nzkGaYDeLRYhenQb8pC7$g3qF+MS7U@%bOdfz4$smrdQLi>8&;4<-v#W{aF^LSCfnHVD z-F<7U57E!jEtZDw3g5P z*_3x?3KsX@x9#oi<*9F}NbMG#opkq1HHG>TAfo$XFutGc&Q4FxwGCGzhUkBFRU^nm zM}knAWgbAd`!8O6ADT%?N$~}+L~^K2E#$O%RnT#fDfRQ`Om`)D`M|CWH5U3icQjLq zdQ^UQI#5zlPGsd(sU978x6*DL1O-xX97mm!sOFQ2dDCii@wR#jwwfGAX4=r3y+6JeKcC#b2gs~IKL z<|p8GF%kYz+pHBdcRpB6pZaW>Wc-_>Sk@b{uBN<@nP$(8AkWmfn%@~UT{Kd~fDRtr zwjHfdFUYc587kB+v7`U25O)rCBY+N*!LMf5xF37Aj?PE@1mZ|ULlcr$b(q;)h$pX{l%=je85D&<Nphy?^#ua%r^s(6^(;+1Xk_;-Em@)M_`#;fKowaJdN()ALjO;Oo&4%} zpQkVTQG{)lIfK6PjjQ{6$8s}NF1K#s-tDvVN=qYY|7obG_~79~DND;sG{P0Si!`n% zZvJA)Z=Ve)f~8y2f;+pG_&IBTh*#=+TvFqFGqbmZaw9pKvx$fy3%&XT=C%GfL=s|`cJWK6O_isR#rjKB_5w1AC%!M=IyMF z(ZU4$;2(3tEY-u;(&~4UI1P}kMwj15j~@9l?-I}mivdUBJ?NiNJ|3pNZ2j!n#GF?1 z1x(5i`4Gp@F@wD`Y$D+z>+KBf)*{zYQun%34! z@cw*nR!pPS4ZjgfE4 zxPH3X-Y1{ki&e=_)Cg6ozO;-TW82zc2zc1C%H@wz}GHh;VF_mR>Al z4qD_XWnoa1O;a^{C}LoX7w5|O)%|F9f4z>t@fx+tFLsWct}1>0;VK8rz164A$j2@I zyF$J9f4+Cz5iO8$mRrTBu|=)d8P*(F)lA-aoXDlD_ESZie+@Gz1w$NFA&fE{;bc~n zRKIz(ki+0(*_)9xSE=Bsw5`#ujVn1^U zRWmZ8-#Wwsg@m%|9Vy7k>sm7}!DZ4%4gDC- zo*d@p;%})8$MVNNcc_3#BaHo-^Mdpt3R4Kvccn2&86|{Z7g3#?zVu1DSIc3yVYu?O ze|kGfp@!5Q7lF~g zc23I%(_-oru@WwRUo-1_Q2d)MkPauT#WYN|wYN=7QvEX&{o6zQMcihk9HVGWC76<* zmG%s#qt$ue>=$Bk@8z<#q_i}t;1&A3ckKn84_KpWhli=EPj)G3?s;VQ2|R!PJpcQ5 zA9Yp^jXxlpsc%GPt9iu>^ogCJXtQ-~K$U#N8g@b9pezS4 z3KI-|=B%u-!SPe*5l~YpU>+%GKoec-$81gS}kgvUD*m>+2H|s|#|HQMCYslaz1V zr4HESxx*{2`I0%pm4Sf)&(zGUc3?oV-v~7$Qp#mzZf?%a&nFeF@CG{NCx9W`w{E>o zqOTqxRA~s0hg!cAf*RTBO z$i2E1mwx~2_iXQ&IPzDAw~1c9#4p45^Y>35c7=bzokx@QmEoUHUv5DvgM;yY|Nc#P zCE!z7%VTp>tXqtX`hHT9>UXd(u&-PMW)9t4Uq)g^xiFJ&eZNJ5>H3JDuP@)*V8y!% zAN~F3@n96ce&r2+ze`(hd@(aRg>o`Vu=E1T!op&>+=<{4CgwFtiu;@q;y)%bCDpyI z`8NW6l}pBCf)_qF}bD;JXvegm)M-$wQi5%?Z>rOw3;6T=EaFERn)=sG!I3)no90L z4j0)L!zIB^TKDs(Vt-O(Y+M{U(5CV6anJqzS9)+#Jjy#gj@oboj8PsPCo^p^^x?8y z=_-uSe7OtquPln+3^cUBc}; zE(uA=s31K~zrw;oII@gYBhzk$jv-k-Ta{&xH}0}Vrur|(YpEv3yii0A)a#$VBH4J4 z`1{Mc|6MP{#Kii3VuFq6)4(frjn(~s4*A;uXLhdBMadVKw!FJf3)5Dq2wNRa!~uPC zcXt;wn@3)q7qGA@3bA4WZo`nW{+-g)B9O;`PcV*czz$p)~}+I4`)EnVH85phz+B@oBJZM3R*1h45MReUb_y zwX?GmgDzd*e)KY@h{tu;nq9x_syE$@(2L%lX%AwIXWQd+o(1{)v)*CeyPj{-gg--o zx=bT{cZ|nTV_OobOg5w?QZbuaT@3-4J`Nm|wF!)nx1j4xvl*&;16&9;+cUASPCli&?APy?JD zEpdVH5zS?o`&*7aN}lNIRV-RsTJ;DhJrBbH?JXG^FHm1>d>Y~OY*)ZEP%b1u zyG0iJa)O+zB0teuAPqpWvXsx zsUL7OJ`k9k!U!!em0*rv@2>nY0kIk2DVlUv*+Yr=k9%cgX6B35D>^#~XlQ8Qw;~`A z;sC*?60k=kr>@9NBxNy)3$WWBmc!A719J1?IsWFa zp@w?5DMAKc0mG|VLZ2ilITH-*{ta@O+*`l=B^uzO^3B>vL35~UYGN@wNWNl<_AL~j zgKq&dLjpw>MR?_k_ZvJ)IM+F7qG$>1`_t~Q`1tJXa<|WEPY^O z)j*#HXbEsry2>85T_c&H8wM=A*x1;|umyexB?kzeKDJc{(g(+&cXXvaAamQF@Vh4Bg51WZ70o_A zqGw1xhEWZl{5z^DL;pr~EB9!zH!G?VA4aVa?IKLn`ZDC@rAouxfW~10`=0fphomGH z+7?2;G2I-VX8GFRKak(J9w#*|&1h}368if<(KAXgN!+hdQqHmI{>C~0`vz#Pw=5H) zN`niQ9_V~gp(7(BQPn~M0vMi~Z(iSNf6s?Y>%L6{3hwyIN-$^^IlT!nuo%&@kx?Bs z(59UBp%xNw0AV|2P+9#-ma4J`u}{OcLidU0hteDN$rl>4H`-Pfw3*K}0VbC>mQ{Aq;AaHGl+<>05kk=Hl$6TSE#?IEkdT-d6L52C zDpOSIks^pg_d!y7gHL@4)+H7e)+gO!n}#B=^;#Jm&hv2J_#+w!lB{vPKaqBon*eHj z_U8w)p~Q@gr*VB}%pm(yT@PpDH{t?}%*wAfDr|uC2%HaZn>jFc`L+sWAPH*aUHfUA z)8nm&K0ZD*V`H>Iq#Uewlo*m*{af70OJ8j;z?Z*Y>nIY$U9B8!dk+3%{5^!G>+!X5 zaGb#&4s)xd9`fhN`?|_^vl+@c8t~axpOB)uz?Na&m6Ws{1d`12d{PoToEij*Y+_>K z`DitMam^c7SwlsHh0%h2lqv6x8#uYSxxS;aU3X+;WT-@3t^%5rG&g5P8{!JqJ1Pl+ zj_5;gT)i}-_zvnDiEE;t-v#uggD4An8)!erJN<_1>+8zVJhyI17#k;VZh+B>o4o|! znlC8IrFK<(h$({5&T$Mk({ZzwHqZy}^5=PEF2ol+t+ zKUB74&{xUq8~5K$gu?|UY#5+y^jrlMqY*5m$gO$YWF_wkn-M{LvMtw5P`x1F1JPqbT`?9Bj80EiET$E!dp8#4anJvABZ3T zR489#<9WkKfHeMS+8M19o0>8~Qw{;~KLvI_CFcqx8%1Xn_v!&jEs`p^ahp$?8e0)8j#;rmJlD0*p1griPXafIw|wMVX}th8<)wDu{*D0}WlTK0A4s zat#SA6NP^EI|rRw>Fy>rbl)KXjNnhgrui6Jy{#jfcs`A}!N*a$FE=hF`&X_z6gXRe zoqbSbl++r4;6k-*0u(&4u<$7PL*L9JB_(`Ly^>FTbLgL7c6drmq%n^qHgLhLhnI>}9k5T}?aVHJxs+sT4C_h0+rN)8xHnBF{w2fy zvYpS7zpH2BFgU0T-wOuDwzO{tgqnZr$Ia~~@S)^)Q~&d`BLD5PXr1rx>$k(-!_nv5 zrur+0px^#K96CIQ{&MxyRA$EO*U29tkK&YXO#erm0O(scFo310sjB@*^OxgY*1xnb zTvheAd-JpaHy_{Yv^2hY;{r5~WnkrpbcN|x>(tN8j9X@cnHml~jh=MdiGi&5tEG#HX2$9 zBFoOsq+?;B%*Z%{73uj`1HcfIZ*>6lx%gLCs5CY<4kWwT3A0`EcQ8?OY;43=0+Mp; zVDbKDd-F!Rxuc`U@fzyyaGZm5Lx%vgeo2ZeK@DMTZA~4#{~0pizyE#Av?g{-2`*m5 zKmoLglskcPENy4UjZSuyI?R#kdbGT|u3cnJo_LSTE&RM=>wKU(tHzN|n=1Gz+zzt} zm#=Y)eR)m@i*)P8#zj@NZXowBsO1B$`Qtq{dx~+isNqO`iHImF=~B$_Se1LZ!V+>1 zIwI;Zv^2qouh=3$PN*3ie1vBAD%TyBX7zE-(G}*Lb8xs78+&a@kSW;I+?-y71Cz@j zb-XXfHC-dz08=a4KpP|ElN=i!L%9BFwCk!%esBP9OA;sU(h+v~Cwn8G%h1khbP+S@3)%JWqE+PTUgSTed024wE z67YYON*BlPwqrEt%!9M&ojasnUS4QZ6IT3VV`FcapeK6Fd1!+-?$!lpENJr;B*}b_ zlS8)|IH)W8(Ji#Q3s%98A3tL6A)<%7DsXr-ji9L^bP51$u6K_X$kew|vgFS-n0VktYDBRu;3=Ou1Xl`i|McE3Y> zf2)VZz9PtV6!}gTj(z|B{mq{R4`pRhC~&rAW?sQ;Jt%A*1)TFMRgRe5u!;(jR8rtq zLl4{lw-60Z}cr;UFR>&p_C>jt?A$E1ZcrbiSid=y(ML1ooG|b;3eIL5GGLn)ve3Lp8BGN#X+V zw<$ogtu7F-2!ua?hi#=+3?2?k(XSrh~I2SH-G<7)giDInvy!hRg zv@g}t-d>yVXHR7;Cxjvmv=Hi{=77Qt0lf**9yYep%54zRYas;Gm8rotzN35sU>5_G zt5@m`$TBOw9~}+2hZr1aC-6Eqz%(|8(I0dGlSytZC_@~VGKGu&7g7JfZ_E0pxxHOI z`srvzX{!KGvqn%Q)I&j$>e`XR7qDXXAR4}ElEX2uuvkedh`Yq!{QlZl!69BO!Y*zR!qX_P}}ygc%iO5qJ)m8$A1*_qzu9G+~FE2VII505q z7}TPHLMt)|8WlW6x|z-XR|Jg$)*k2r_jTUSI-CV&$t0kM4YrE0%c(H=Uw}82^Lr`r zsQ9{*?a}oI(KX%OYR-DBQOV%a-+i@C2Nj9i6xXapqd&nbhOF+aQg??=*ftl?x!0eAt7kB>?Ne-K%ar?F}D;|{d@|WS@FFO(EAD4 zEB--2Y<2~rj{g5>(?8afX|Gh_*L2o-?;;LP4LXkjRuUk4!i|j$H2X%E1~Bmp|FGuL zI2r?zfQhe7xQAfGaO!gw#Yd%GKioyOoY{Ct=4yR|{0A$}14qI@fmGwKk|I+(QfW#Z zd*~ywXE{)8OAGF(#uZl-lxH7EBG`+bUZ8dJ1XXk*^xi2tfK3W;+saB@hXTC|;Asx8k6mGCxdwTYY+n z3w0BsbNSeL9cQ=r_@qGJ1BlEGB*|@e&;q7MbrA29MQ5UaU|=nDH*^dg3aLVZAm7*> zr-qyVxmQ*z&W^&3oql>>QDz3#yx-m38wYc^u;ZWV-9?g`KjNwULg%0mM{3?Hw9Lj6 zWI-U)P%hugzwp7NN-E;B;(GNbuJw79ce!I2{lKP1m?4wOB+m7S9l#4cp9%d&3091to>ao9Kc zz%AB)eVq@y$W{{qT4n|jM(SK=(zz;+DQxz3j`4A2eP;y*B>eH_bbwv@gz}+wiH^r$ z`V%=36k5|m)3nH&YU5B`p~j%&d6?LjyBwbYh&q74_v8Lmk@YRg6nkMtM$8MlwCpMg zGaLbKA3(+T1}&I$e;C-VN@1qHS7%gTYJeJWnvkxps3(J)y+vhNT7I>^GUhqV91jqK z3}VCQ@>i6%+ObmfmZ34HA4bN+Rl?R>oG^V}@yk_c1|)a3ocG1cIS|;E;dpPu=x(BZm0+1jso_ zUZzN`q+G`(#rl!s;SMvIsV5{uh~<-gxgm;8D__(&p>N2g`{am#+qqS8d%0J_xxFya zD(-t4M_FKAMK!17VwwGQ8n0UxSyZ|u`Qr(n+kgJt7}@1OC|0u56lZ5=(FXncWHs5V z$ZhQv70inl`Bf^*L`09*U-O1;Je!Jl6?y#Ri9b}2cu{u>bnXy9)<&RN&??E&hrB>WE8{4K@FZCu8Kg#zmPu37``$VBDI}tR{NT zZxxJ3nOx|Slay6kupT5eKbXZ*TYDcu>CY4hVgtIr1dU#lA;u;12yvJ@ZGhSW5eoQU zf!&00ywDz3mELz{tCi_8Hvy#s z|1J)$esG4`Ipt~tiSO1MoOj$Q)y7L38Vq~JR7YR(1yiEHwZY2kTS1gNd#N2}+v_sJ zxXpxpD@9THwqqVIc2+N<$EmlR1#AVwX)D!%rR|^mM6o;~gt?BC7B0%}uxG)zQ$q`afJ7C>@HV)1Z(n<4LMXWq+&3U_%-*(TRckI2- zy2VnMzPDIB7V@LhR(P@3p&?VlFhy6(w*4cmPjmB)C_xxJALd|6la(PmhzJ=UH=X*2 zRtHK2kr}TJ5AIKe-Ya>Z(1TgPt1Kpe$Dfp z^K`W8mtYSE!3oR2AZKIY%SWTm#7c^Oi9-87^*W2$}A1I>+WLe%lz z9c_ceq1iQ^SO?q0Up5PP4bQ7}F{HlUx&hp#OAZbW;B^uA^=Sf(mZ{oIkq(t?QlbVp zuZ9u>_F*@@t`aZmNCD4$phM><6t$3}@+m&bsxMpOHMR{7E`b+Flbr@4kBy81rq52* zqM!Dbk<4<%UO+t=%DyDSo~%c^xHYt{JVl z*a@Kw4R!*9KOve-sqCya%n)o-fhdE?++<)lNZ?> za2+bzo0ZGmTyzB6Ipq1zl~ox3b(ayHy@@b_e47y$Dk#8!)jS6h%GXC-PmHXsdE?m2<>k!)W^q-SuKVKUXf9mox;Lu_Z^Y+X6~kiKANhp7A&>x>@)G+OTzls8`3nPL@>@w6#hmo*DNhx;xjOVv%H@f^p)$crOKo5tA+djA3;erKDya1($%ZsLh zsp=_6$Tk$stlgu5^QtB8!J|==LxolYgd|DqTdU7~a$1ByN0o4t0%!+<3UjLonb+`?5xIVstbIqN(|x)`N+#>cPFRvj5nvB?r%Zgc=C&Eel$h*k>PvQ938#_*N5yj zO!ZR`b@eWGA{&o zrd{yNacMSqy*CRC=j%Y%@W9HUciUl&`f$aN^YEPF`#X(a(|%}|?^3$)+A39@Rihr) z`-g&+`nslO@UHXbRZ@<_`#BBU>GmH)O(?2#zT$kiBURU1n5$&myyE*%&7|RYSYwCH zva=FFI~(&YmJR=$4Vod4iv0aY%OEamuaeEL7yMAny;!geKSe=(+Nv8^kLc^1gq+Qd zFCRH;R`p$poM!IF6|Vq-!|48^xlj2}y#gB4MC}2tcG+?kLmRk?$o`W7*=&6>d@{0_ zVp2n+!|hc+=+v{i*Ld@g4&?OpdE8UK*M7TC3*Dbh*AMMe&HhJirVf1jwia&l+C3vG z3O;LptNz@n@@GQ8?`Z)#M=7A^$70U_7>iu^3_P_8BuyEfk|-wCRe=X0!VKC4X;5L& zi3am!oFo{3b=c%{&8A=7*kYH2(Ahb{bX1Bo5-cX|f(>b!;6ke^FVKbjiD-@bmgCCs zgIVM5Jd;T*wye?J+Bn@@ln9xa8vX;xAjdEL0ir1qX4u-O%kZU3^5Y^FeD;E%~U`_LZYOs zulF!m7rzZHs)ZlE4#micpYdrMc=e1j}{@0@4=meTN_FuK0Xa?dg0Wn+`vJ&MPQ5Qfp@1kUaO8 z&O0i~H}{|7DeyOBcOJoFC zRQA56<{cPOm7i6tZ{O}a`uWS!npw1kMI-Ou)Ymt0^YC~{1Y4HjXXyR&S~9t5h7l~^ z`(8SQ3G{_Iwe^YqvZ3VUav;Wg>lEsP*b%nRE!R&UW%bXgx!){{1V94%u7J=ydIkLE zWtpRZ@u_W#c~pjs%H0zb~mBr*dUT4s073@E7`Y)$4zojr_L;Kv?^~#weqPR&?cB$M9A^ zBI>7{tsZ)lf;094eYq#tK&m7}w47cncb$I_&5llpWlZ(*kHz~Ie9RWM#XhkQ?hiXv73Li*YAOVQicxxAPk zz<^7Uk*vqg=>=qR7W&}_7!)dL=Ikwv19_D6=8J3VVHcIzsV}}3Fe020etp;G(llPb zIQMsz@+!zKO*GNsLo%!oSgPOVE2})sYq!@i0elg|qwP14l&qVEmVKE45X!_rl{s!n zLmw%!Il-qzmcO9my{uHb=wzx&8tLt2hPMra`!6bQnI@D;L~{78E7DElGd>R6K}~*m zz?aGOa*p%1^?dHT0Zg&BXbx_tRd$$R4Bzc{b0V`)p`k|oI9ekR&v~Ew3PONhO{nqa z2_$8HFoj^Tv@AvKOYT6$O=iOpqogbzM|ntqTKPwFj5d6>HH<2CTC1{`b#HD}F1g#^ z7(_Po@qX7pH}T%;XtztHq~t$($y)0C;k}XkN8~Z9^Tn18M#7zC%<%gduTvAkgo4js z<>GPMzafkyW^1~G{wEFQ`Xx7;NrVZxe67ncW&i(4=}2EK|DWk!8SGgVsQ$nj1K$^p z{y0APIPp8J4K%Ta;tvK<@Ob>is*$Vb!L$mVRF_fxZK9r`Dz&r&llIS6ziz?aBEBxl z#ZLZ$5E;P4xkuyuq=wG_>T~W_CFd{oOZkU>d^1|PY!S_rb@j&g|^gd=$6s>XI99>8}KIRY?d5gW44vn}gP**!wdb+1d@c2~cSER-+cH%`?a)s(T((@StgJR}!sdnhgZM8~&1Y*20S- zT#rXWX|$cvG0Gel@mp+V=#E`bo25Z?Rn-{D^&EuaJ~DE0^CN8+E)cBv<>Y+tIs_Fi zJh_2>rEsZ-b$0+zF$(pUYsAQ^S1*>9_G+E3&d=kZnjws@lkJ>iNE?9cEHOrf0*Nr$ zjelg;M+3otMxz6$n(7%GfBs;n2qyRi+{JxoB15-0O3ZNa@wy%Ew2}$EKNCc}p8Tp1 z;4}=8D9#nhw|;7uv;%2TLW|B*wQ+~=a1{`35wFh5QmOdQz<+cKZ35P+$8tBnxgU{6 z*<)j;oh@3Jo4-NYV-B>Ak+_X3=H{6a2LQTX!(k!LA3M&YUM!80^yyIbilH~%-)f^_ z+3j+MCzOHp;EM$+p#r1)J>h5R`xD$(y&pYm9;s^F-|%?SS8-D`j{m~zFRygeUFq^3+JEeQmkZwqHWSC#0Q{7OqI`=5~Y|=aLc;rS_?|cjoPeT95tVm&DMfqpbS_C{MwK>j&+fE(A38L<^$&2Su z)4@*-$)$Vmw*aXxyx!A-_~84%5G17U_w_c@YmHJXb)H@)y{dvD$JM<& z=+L{ASWfbXx2WbsJyTuogXmZ`*aGl)dWb*~`7M`7mz}bQ1jA?fG#=B-j3=78ZV#$Y zH?s_08M)-Thf)ej^N;jsIGA_#*2@uBw1Ewl0)Lu>U-yr$P3eL6BNPhptRPy&K%oK$ zYJIo150`A`SBkZswoImF>Ip~_2*0Yv#$V?8@RM22<>k;-JX%qCt}1Q`jZDp7kvAiH zj(#<9TDn3EuE+bl1E15X8y=0Yf{KdD;=NBF!FXK7II@_-nE2?jru17Sm*VW5Vqj1#Uv@k_9WM2+_U=u2%q7q)k*^qmP%ZTu*(s3M}T&5~tBl;2owWBhlW;R}J&uv6haW>fgT||iTUH8MckSKN2 z$3}gMg>=Q@_JX8g%tSHz-sx_X=-CW1u2gYiqLdYu!-?RJI8<@g{rhOwIE5<;$KZ~x z|9@L%|F^OEq%J3WHw~=HgR-{_;31(05J9EYb|l<;G84#O=(E-R6or?Ws2fUAANM z+R9De{+k_d`Qz|cKR*%gc45El_GcRv)+GI^m7P-V&l=1G=$0E-I12U%d%?q?| zgBshQ9&&SIh>H|GJ1CU0sp!G9TAENo*-6MBd$%(vnr+5~nKGfP4Vn__FCr=UpQdtE zzm52_qWR%YPH&0a6xE+ocW1p}&A6ZDaCJH=zf>AD4WHAYuV&ihqzv!9fI~E-3sYOQ z)HOejhR*-+Lui`&%v;`>xvca`$;--itvC&TZsp;5&tvl0L&!~8kU!01-#>{$z`n#O zVJoIokCVfoLY+;kB7;$8QX|ch1S+1g)AT0B0k}`yw<4IWmiqEc3w3&`++7tBo6O|o zb4!>vJ;&cgMy5uwjj9`~zN2xTPO@I(&@a==8gsI-@5U6-(BzCzrFwYo;eOY7@4`_y02?pYpwHK z=Q8W<4Cfh#Vi`^j&Fu7+yv8sQ1z=^U5Y4e*uCEZ%H4*Vkp8jnsvxB2u+rQlQEww9% zne;5B(g@5Qc3mGsmX}^ic?@u3s1lv$XA{BwG9uiVH=b-88yM59EQfS+@#=ssld{aq z{>&g@x34i=f~-<@C1v+b*f`NS;I5qLRu-A8Z{YhO#$v8-9jd@HljLI9s;=6&mQ34> z-YZ4W9Shj2A&eZ_#X7(r>S*A>1u)vqcHZr`m`jC}`Kh#gF9yeZkmzpbBk~3^0*I$F zO*Cs+$tqyy&#xw%1t~Cr<a`IF!Tg*&X#|zW@ z3v^M^^2UCd`XG6SCH0SCQ6@gMw=WUY-0!RoI9r@fBdugL`-Kan3^Mjw`HW@xD**=x zILr3>&J~(DNgKmnSdX4g{m|gnrl?opqO*>48)K(ug$l~6Cck}jjVbpxZkloIDX@=$ z{Y|aLPvFN|3_vGfvC{vGFoqpz+LjMi9hv`L+^|yBWTk!W=n4IWrN)? zo9+6A&0j9>{Z(O_RaI4$BO>WIIN*BP)MR{IKj0cJ;+*N!Z(Z<6`>XVLW-x=`qg;t242hgrq3N7v{;5^5 z?b=QSpuj9uZsI}FPVTOxq{(D}{=m+aTBz4VCDf*0P>Ke%54Y3@TVPw5eIA~HRh^5Z zsi(f*-nYRj$f^mmN|AKHBO(lgsr=@~nR1zli4|xg&G}E7BJ1+yV~5RmeZE&_XCp>p z`fR2;lqgPpBRt!85KPxqbU~92F13%=oh&n(D_yjci{!oa^l2}z!MFRG>FOdsKe={q zID!+!XV!TKb)qOvmnU5Y{R`N|8#@hJ&t;|E_8_O>aZ1;9HG3=Zn%3@C#1)`yYkho_ z`uTGeuffuFxrmj>M~}4)ff^g?@)~pg{!6czqf=yI;W1X3zWvdw$lf(}3DlWecq-#R zSDYxhD%`s?+uQ5B>LLY}Pd$G%tMEH{hSu`DX~PP`8f)+L6amR6lyT|gG_niTZ9tS*Y$NU`FVzDLauLx=#zqdgLVfrE+^RPx8+e4U27M^w{{~DEv zv%~hD{uP??Zxa*`jVh`)^m88 zduw-t*-w_!&4?57z~C7xBmOld$zU4?}AxFPsm3r zgCWv!`D4GkyB>c8^@&GnDUHlNvQ|mSctkdg1s6K@*KU7Dq9-_f>P{TNH z4I}Cvo(Kw> zP}NgCiwhkku9?A(JCk|w!64y4&Fq)8A3UY`%|kr5x=kO}*WO$G zr>FnCcb2WIaj}n+b?jUB1IkbK@SBKS^b+scNh9SuOWmI+M?ah9|J)xWe3b)X0Ed?? zdQ@e<^QUG9Zd&DfBV6d7{k*8d%OcX&-XvdjaL8WxY*WsT#2y{0-~S!C|KO*rUlvq_ zhNzpVXzZYKww!a%P5LzfavkB{A1OS3a%d2Jzk4K%`|a+P#$&Dp)*8=xkpk<`@_}C! zk1`Ik%e&u zH!(7&x%%IbPEY#GRx5hvrI$%NGD1%Evx4N7%uf=Ke;5~c_db*R;%-Dmem?o8R=|bj zyhL`}(TP@<(73fR+W`tI`Sg)+JpA_h^J^KK=K zsX7^+G`t2behn*Ir5br!xdh^k?PQZ1tBSHD6C2mn-sQTD=4`WZpF%U^)?WtL)(=JI z&L8~0`_nI$6XV9CoLok`OlR%qwv~n-g8Tw0O*nK+9SIq;sKCQPTBwgk;B7;rwYFDE zPSd_1hE}Bh5U^88bZSI8CRd#Ec3QYj=Z5Vx>l^o!=dck>lIT}O-e>eyls%YoyOqD( z--I=8Usj)gx?woGLD5rMY~ztx6o+RMFw8tAGT%|$T;MQxV`Xq@Bz&@nOT0T^0)K+#8f1^N? z3BgsI9}E?o6?e~Y@xJWDaJBLMtU=~;BjK)r?k%BLQcurKQ!fT!ws1n_r?_1;( zVmSA{I$ZQ&Wck3)S92AM{728O+T`<-b%!wex;SMgA$|-aBJwYy9i*oPNYn%4rRDdn zb#dwAmqk-EOvF?N56-K^Jd9tu+s7}Zyv&X_x?eHi6`;kjYMo4kP+Rk=Wp8Hu~ zj_h=vo0%r0VpGa7I>J6#6ku&yL-LZ^lSG4u-OuU`X?wdmFOZuIR25y&KbM`bZ%=BeUouUrrbKam5S^V7K zG*h_=>I%H}t&bWV&C`nQW+0kXA0wv|bdqf@7w+wCE)@uwNUS?)W1W1i%xwF*lWKEw zIBv*H-_dGqh{CVE@2*V8y64J(lWw$-Qj~=umsD}KF*Rws=6G4Q<=dVpiddwIem>q_ zbnDK%>sG3+cCqqFiK8kW4GrT?ept>>X8)44k)Yny&+Fri6^oIH=WBQ?iYYEu%r``? z#9ht4?b7MHc4*~h-EKA@QU&JDJdwISRK-fV{_b*nzP@QbaaOCKt&bRbZ~XJ4f(Y{# z-<^p>?GoEg4y?IyL~(vM!{b85RWu~;o%G^=DK%T>^7>79I}Y|BxzUY!q!=B?cGy$dV-Y5JKplm2V* z(${UZGPHS{JAZt&BTnY&TR7poK&G!Kxh8eBMJY`s$CJKneon_9(O05P?u$g^$(^Dp ztYDUu#4c=_3HC@=t=E52>WY#{FB0b>>W6v+iG)e2PQjmm~61;5bvu{`BF( z<~Dw=W;=7vY8g73!EEZa)|$7b2G=SpsEu>R3gy)s1qs_kBBxY9w`o)7SH0q&(#@s$ z!Ax0ZV`E!x$dM{Vt?>9yJ57fZonUx^drN0 zQ^s9qEwA&M*IS1RxxEH1VBTnkJ-LfclSWIAwa`3RyXVp4&uQDYx-MO0ltQAfPhvyb z5#K)+cgLH2%1cDdX!dKPF)=Avi_aoSy)hyc8=;Qgx?5jKRtsrQ;dOMob>CZ9sv~&T zGcNC(z_je6N0|+bMhiYXc@Gy)Z_KWW<$jM^za$DNLEGHM)2y!LTSiYwJE>J=qERtl~LH345E(hJiQaK zQ=+yi%&(Xg6Y<@gKY}k8$KV^bl-_CHmp>Tl1=gnzV;UoPnTPf{SiF)8OVP2x3hZrjiG#W$Bj7#rU$FtCePXi7EllXD_&8A_AbG_5Hm4PD*~pcCq@6WPP&M zowoxXCh6}!$)H)w(Rcb;Be_`KDgj+Q>4?E zSU|ANc(lA^%{7^4U9&V_lau1tzCP~ybu^jl`sMYgagedP)(G*c7Vk8Zufx>Yyy>T2 znMQqzCu?4}i0|{#Hwh)Kj#azh^vR$;Dzov_OpYFhHcVS?9<1;QN)`hN<%LuibIO2% zwY91=EMBQqZ9n9Z`pQXSEX+t!T2@0LBiXv_cd6^j>3lQmj#;QfBtJK(#z@K-%)QI= zqbzB8zASh=J7DyeLr_>4E1ocgT|qaIsuvOyLw?s|Iz~-Go#eeY-<=xW%>J)3Ln-_qI9)1V??BnbVu}2#rfVDYzoX3ud2}5XW=V8Rg!s zytkWMS5ok&&#Vt-n;PP(F#f!Bnn3D%+$S9`BXW7$QkJ5Gk~KB8IL_=GpGn`jz}I+0#Wy@ z28w&tuCn`PH<5t1&Z8jDid%1}6+!*WwK1>6&uof?Vy?1QCDQ547!N(}1{Rf8qS`0_ z^^tjYKt7BX|koZs4yAh0X)nkfP+ryZJPZdf--*?lysSjVitpDJ- z0eMrwtG7Svz5F?Uvhe<{*Io?hA-4;HC3COfkyUoc&B9~$()fP{%X+I4PB~T|)E(Je zb1vtuME9AxFlGf$@p{>DW@mS34&#FmCy-K!S>Y9geDWvgK$PV8czI^y?>O z+rz%J2lRQr9!>Ib94?>(=z#65lK!Yo(&U}bPxTXSjQuch#QgC_mN_T=0{`8Yh-9t7 z$hBANyUDNE$2lI?Y)@ho({@1zaE1O?3IBgYod3iG9GYO~JS1dfBp)UGHnyXK7eWSH z=sry0Apg!?=H~5^K43n0Z zR(IKY>f!0hmShEi<|y%92#RogWvE~XhQ#wSjhb4%&e`W8nEfB#{NV$jK>QS6THRQ^ z>ek!GC?N71D!L~prQ+mNoTlpg3m^{ni|UES`hv9biHV4yE7p7P;%)%^b3EoUH67j9 zG&kf;7}?mA5qhzIdoE;NRjkgC0zk2>S0dcEj~%?UTJ(F^D*0A0f0UZ${}JsTa1iwx z{`VsX@vn3q?ik&oo7o15eV!<>@ylnfeay;e_%qR_>S&Z|vYl(5g?)T9vVE6{nOWDW zY&+@ts_l<&nrZ5J5^p;WV=8ws8R@*OTIZ1YwvB|4Z-?jZ9Xwv zeel7cTv!HMY||Dgm^G1EGS?BoKG-HsDT=UpRVj^bzKJnbGckmtR<{*Y%(7#G)P;|n zg2m_o6%~dZwby#eoc%M)i~V$?*WW{Fr1ng2o@N{2iGg?q?FM2TH2HSZ!%L6mhK9sf zsj$@}Z}FA*h5KCWmI#B}+S|@d&151Dmy+3E>=0T|oS!&XNHQ-Yt+0teaRpMHE5L;3 zg;TEGKyzpAIEP2yuG5J)$#OM1+ye0)8c|c#SElxDu41(Y@1n`2MUK*O z>-gnTNNy9edSf%O#d!JIjj5Jl=8XI0+%-Lz*j#k-Aef2|CPZ`F`2o3temi_mUWqQ~ zQeSyH>xXmm^|Tp`yDAfY1(zP$npbk9Ks)I%el)F3(kglK*+F{s+n-bSE*HSJ`7GSz z{N|6+^4>&`!8n=IEJ_@@Rn-xDOQbPu(HnAf_{tZaMk8OFDat5)PL;)xQ?nUa{D{4~!0EN1^X)SSWnD?+xoy4s8YB;4g;Nd~;_w26#f3BQb|fs0K>bj65R zn{i#viptr7{tH!Ql1Y~=>aot@ZW~7L%;)TUM6rvn>O&fxy|)1#qoSq@esWGqtMI29 zA;US(pqyJAl7mq~g9#$eZh(0WFy09JWcW7<=_1@>`%=pbtM7&>9~cl`e~2T$)UA&oA&*z5rWt(H!X!QIw6?=;Rw;Ucxi^S=3cMY z1oUfLS|8WgSQlv9Ea=^iCV|;x|0TO^?>(5aDVb+XP=>n>(EE7bW;Tywc_cc9yo}Oz zT6dPT;mw};hA}ZI=|_pKZ#W^RY_1y_Ipvx|(vA{-s=T?%&{DbYpsh(WeNiSZXmew+ z*_rDd8E|;oO|uFRk}uR!Ugk>~yaW2G>Le%M169qV=q2K`KkR7_G4q2NQXye@0 zar4tk@t*9=%t?3hN#DK9mMp#Isg3RGv-QhfH(~YfGq%4K7=w9CUv&|wy4vyH(Ry~E zOft%R>iuclySF9?RGqXG49?a5DiavEKU3D(H}b3Y>T8M~2jOU)&8AtAz*IxgHrsi( z99iZ-ewTcuX<|lbzxnQS$#Yej$HV5mU7lTGamEv+IyldlMa?$%T2yGJkoLPjxPzvn zL>%8WJmXu2QGox2*f)ymWFa=pG6EMyZ@k7H_X z5g7F=(un>^V?~HctznqQn}iFr2aeQCHI>ZO^0*H=5@%6E+38Zu-@7o|ThSU+*X)$l zpEVd;o^Q5Uf6kDHhi93f(W_QbiQ7C!PoEhtiTFF>=6g%#->nG|Zg!vBFJvq-N0FC~xR&w!g6*I&1r{2sKnc4`FUvj)d<%42!a5 z3){&aO%(p=FPl?cjj&Jyv2u2v_C#3YRMYtk$S9KKKAE{H?`O6__1$Ui|3RLaD=P?P zH2UOTvrCf^nL2Dc*`^5vI0O)PS#GZODRs>=@0VsMtfD9Q`ybz*Di=~Hv7c{+!t``WddFJg7p6MWyLDtr16`7Ydx1x->G^OXJ3Dj|YVp}W zy23Vx7*6E`=Gjhb&~WP&>Trf`P)>xJY5zsqu|+`s2R#{x6>043A@Mpcd;v*E%pra> zXy_XcroQR2^^lNBNSR>Ui@`9N4{kG1om3$_>5bX?rb=3jk?8=92tLz}o7x3#i2@qs zOj6++GkWOGy1w#jQQ@GCZGSkEqM4=jby{z;rx{KIJ>pQ_wrneidE<)`a**GQoG{d> zaxvdh8KF8wpR_utodMO=sZPuIrH+@E^N^iAa$#HPG`uNUr#Qkli@(Jdn9n+q#>HaT zu3eL&6m_UTfd#6)yZ(D5!I*GT;dLkcln<4K>N*Wv<_qTM43!!AD#GMb{T=|#6ewIT z=RAy2JoFp?tu^JJF4-}I3_ix)=gZOMT?gtn73dEtK#p}Xw_?VQVGhZLg>gCxhuVm( z#r`JyR9-zINGA^odkhQn*6ZeT=mqTeO-Rtgg+ltC`IS^1_mL{p@kX7pL*y4Os25?) z!6zO1?OA_$wIBGuZyQg22TIB*`(41{|7QXaa_H|R0D=Gcw-SJmgMTjp2)X(95`bX* z|5gGJ^6u{?03lv~F98VA__Y#1Rn!lF-gb@B-0uZ9}_Y!~*#=n;Ugy8>P0uUni_Y#1Rs=t>2g!2FYZv>#k zn?;k+byqZR>)jV;IwugiSFyP3e^mfr-lz&!?KExWf}5}BRh-8w&9XBpjp8FMpL63reYdhr~3VR?1#Bqp{6T|TiL zKMT2Cu(#h*e0AN6q1q+42hmqLTuOS-2VaWYrQ%3`%D1qaD2XZ|@7sodmzMo@s7>S$ zjQh~7-D3_!$E>CS3uZu!dYWGi-&8OW!@e_X#^W}%SX{n2YgN+U-6>+bk((x9l99OM zbh+8=Uvhs!KWM;ZzeGv+n8+NUgD|pQ;!)Y@&C`C&EW#dG*AlF3;yg_#nd0|g`;)h>V2JEFeG>}zehI=gx!kI6hjD9>UX zdl2Kk^_z^6dVknzo~XuFGx4QvKZn+K2z^;rX#E{YlbH{lYWvxpi+?;}4h39p_J>Hn%?QtRS0F8yDx;A%7p+vYXj|<8#1vdV)BJtd`HY;$Ee z@yZ_1Qt*_|U6p9sj2cNl6`v&EJf}t{(%|KJ>f%8-NImlCnhAi;0SQY>N6a=2yntg7 z>tt(udJ?9E^lmQy<}tUP0vylj(#*}dEy?^bpiyM~kfWK*93T-^pE;}MY^a?kAnJcM zldtV}#(YZLWxGeJ~ea#7g|dK5^-}Wn=Fe4S($DnXCKZo``eev*oX_-TcrTusq<&fx9aOwN?*{`Nn}1bAt}8C>XdAJu<=dVuMXWA@YjPWm9* zj>@gAN>IWR3V_gGm>%Z*5IR$DDk7HGBY{}#iao&j_AgKmqqqwY|G``MRrF`K=>_3o zC|}ymZT&7;gJlJ0{%B-hTH?4OFsobRWHJgv|R9L8u|J<48@cT znj+O=S9qQvP0G+$h%>d(evqUPb;DLAs%JURY1u0vp((0(5_E!o<+IAlL4+j!dpVmB z(?c0E)@`t_%&c2JU9c(pYq`x>tXWc3wp~u#kU0AsEPCg}u#SL9q?kBSC8ih17 zK>1}U2!tea?HWDeSJ^|dR3kcxctF!=g!Bq&jr@F!FG)eB^_M&`M!^0VTBtE})nHK(0wYPD5Ql2Qu z#`nO{ZG1BUFV>-=AlHGl?~j9$kG8MP0!HhOl||>xvY~B+T0;m|XBFkLdX9k{!sqfu z5z3zG3YwtnH1>n;oiE)=+fXx=Vlh~=?2jyUKvRI+$2ImSbQL){3Ghupfj{)MnI86H zP=y*M-OtDOVW_Ckxf#2k7H%1Gz!^_sqAp57>(cmWC*$|W7`Cxc5%HFhV|!c~N`w`; znq8y<4#O&MFgpCFo*!u+#{0L^p(W`1<%w@s#n127cZ)M%crHm94}7O#ij-AcJk`h~1<87~ z3O83KDY2bWz^D3y5wkLb#&83QIWi-a$b0~K7*lVNn7c!?uXKy4ccy5T-I>~hiTu5n z?9n6=?Wdg@$Qp7!moXOUWSY)9j~2ydJT_ zr;d}}{$k-7=!JqTvRYeGvw%sJr|*FNz5~b#aV4vwHTIc!9?W*MKFf7eIFnKxQN6+k z>(}%5`qj|Yl!Kp)dz)KJVkW9ZR{DIVUBEYlW-uvMWAz(LVo$Papkv&>%=Jg1r_C{_ zNQVaTM<>MYfev$MQ~x2$%I8HwmdKDhXjvt}gu?H*yM+roKKj@JTkx4ntm_?2*aT_@ z_VhIW{p>%m;?c8#8|5=6TlNiTtyu^Yh=Er+-?o zTa|#Rjai*WVwtuy|0=AVKZ}uO`fW5lAJ$G+0+TWa77IHi1m$$#2669{P z<=rm4cxe{m9=(t65LYbC!ke(kfc9ZkW#w#HgTT*baZuq|&PjpUdu*8g(@u{4V5?Hc z(bF~aAxm->&fh&;#<=IMw4pT5%Zq=~eLSt0=FUs~2Sep~Hfb*Zix;2QKi+d|@=p)8 zKc!D6X#SbI^fHKJo}sq_rTfOoGUtFdi8OGcTt*{k-9vZ5tD>{(< zL|Z@ioZheHP#0_zxySHT>2kMnTC+VEjc3*C4LLJ^(@nGKgRr(2#R3EtESobX6Ix`%gZZC8>eyeJQ%B zjT>vIYw#&Z!dT|59VLfr@Plgr6-pK8?yf^e1_rzG|+ zMrUWIr_P)h13Pz5*-|kD3bS9-f- zu6<8l{tcMSy&RR9eixNsP(IzIX4kzOSeKrV;GmD_MUK^qpZ8H#* z(}1=jt)O5pZvyJm5I9-CE?Uci<_uHaImw_$r}7^41C!B*>CWC$_!JP*5hf{U90R|P z=s$-3>~Tb0XO8BvATFFPh?%#8{!|vIgzJK&GXYwfYMB-RRAz#X)R4F6<_wP%0+s%K zm!vkJ@YI@4094Iq`%6~68(1|mBcabvnuf09UHE=E$oDRiZo)lN?GDl?8xM~P2q*+p zR8;6X&+?Ixkr_cUszaOC4wltenIad);xmWjEa^veZG7%^GrL`QRge7 zo>wNYh>}M~5k0_=?_RiYVX7w&5Bjfpuzk{w)CaRC!$?3o8!WVJ(tQ?!&D0SbAKr$` z9^Aco9jq`TblV|0L6@97=vhSDzBBpia!VgG7ytjI?qLd>JQ)DqjdW{6u0Gi$JfP4cgj4qbI?}2UiVY--ZsbQE)MfIxF^r zuF24Q37%EznF3fy*&gUZ9pZ+YZv%ge0G+z4Yie%nKz-ppxP1VX@thGLVRak&;xySh zLRaQ+A=@+wfBFnB2DkelN)5GmdWTI9V=(&r(CLQg7)-V$1|*cs6|>hY-Iy;`sUJyO zhqf`Q(8`!A!fmck(3e3>m%-)dGqiaXbT7gKzqLAm4lbqkO+fh2ngGS0KATwEX&s!+ zE4V}0Jj9Rf6ice5y(HoF|DRJ8`5&kKzn;!_zk@2ikn>79{xNu%%)b}X|85=qmjfbC z>}=th6^j{@8Ce7cZ-s@O&F(exh!(qWE%UzB9?X!jM7)%!XgN7KxtijGKk*}@H_-(w zpDa(hfalP5bK8d1AA*`KMhMuCf>H*aPX^lJ)L)ND@}|zRwq?A048?WZ2CD}Q3Rtff zM!G;(j}@>VpY^!G#32e`yY%w(r#mLabm;1KOOtEKg6#xSR4fms+03I0&J)eG}mn{zbdF6;y3@0Hbj0w{3O^T3g!>OSn)- z;?;L`2Xs!mH*9S!nuBfP8hm=|*|Xvd$<-5eK`~&f3|lvSzyi4aR!*3B?9al@{h83} zP50ZFzY*8c3fFZqdJf+7=yc#J>x~Lpp@aqMdZ3x9Glg&=0#7fG@IG`&r+1pzBj;rM z{=~_^T!Tg}(s=c+aM8zUY#N!PZ7$Wm=#6;G=6%pwSXgNDqtm9=5?#t3k8!oj72O)T zbkx=01NeekGn&$mMDz(Dj-BvCQY34nMqwh4h-;;@@5_|GhFxA7dR9D}PvQJidc)ar za>Ga3trt^oo`BjmRnKn8T@z&Ul)03ENUmu6VXV+Yv`V)qn-GL`?LT$xGc)u-Q63elibUb zi+B7c;kEIoI#eT1*4t^4$MRGA!YXA80*z#;s0p$Gye6GCSdHak;c@n+dHq|RF){c$ z4XSA}X`hiAe|AB^yU7Y56hqgAC~k-LiF`5SO_T7Q%D#VvQ<@c?_@t)<(WE_zBfEFj zxW72q+|Wim^YMI9icB1)X$zP8qeITkE(mHsh2Vn$D1Mr@g|w6iZ8Xd zcDC0>6plJMxfk0Yvon$E1-ku@G&OD&8Iv*x%wtn%uUjfY>`;Hr?DNYmotgvt_fNz# z`RMZ&|Dc)Ovu987e0j9Av}_wLS$?|m;5=803$&_K8>wSc|v1_+OL}L=@X-dxa47@M4MJ{&4iNA)xHI;F4b#O-!W>`Q% zs!9@G>plk)6Vr`xY@St~P_>O(RL`(+Z_E6$-dRqR$HH+_0Om@pIgqbxZ?V6m!L|G-D`*fEFf3P}Dy) zfRNPB9yySqnbRoxM0_Lvrt!oTGL4*VpvgFSVU3 z%Uw641_Q%*4A6@tIjw0-&QtOXxL6x!E{?61>692m72C`uIoVz!l)FlnRah$dt>Zli zZC|>iP_>FQ&G<}s7S`QDoLD3e`Jfek%{AnJn20LS3Kdmz)V0h)$d#Rf^cJQafQJU*_pb{}Pc*cV*nGM9u%s}&X|T|Af2KWOEZu4fY&^s8W?a_By)D-F)jk=imcQWvsvGMvQe&YkW=a z$Y#`(?ar99^a5KgHCqtTnPOZffJUHPX`Ul8%e{h1^XKGl zDHAUbb4^00pthHXPwMA%1JRB5;paOfSO89fcDLUmX47{GX&%@W5!PBkyrU}@q^iy> zPqu3;PZ0>YMiy*Ik8{8JxhjENUGvA+WE7{SzDoG=K53FAJ8<9>%Y3k?4y&vv^}>_X zc@>iF&mk`FsOz_yMFgb5>FcC;j4OTgRlh-$Sa^Cn9{fGyjU`sF5KgQ8tDNWTCs%+) zj07JzO8fr6(TlvU8&r4iyflHNR?mlF{N6JD9T5D|<_>EFEtoW|eABpVkG~pqX9IOzj-&Wn<=QqI>OyG@ziuvvt-V-l&}_a!GLv$u#hhd@R2c9KB`9xrmvk#g}9PZBdknk zDdmzCvnZTQ8z_N6nMUSWp5*!mS5st&P1*PN2ETbXL2Lc-zKrq+vp||!1Ak9B$(hgG zgp+4XCrr{qZ%3dFPvG7FcMFHTQqmRRcAK}7<>m|A1j&u2H| zBV@l4dE&%L+of71UmC$u#j{|xBmxH=OdRuQQu_Fv&&hQG zPlJ%Q&PcUQ2jV#Lt!I6z{N}kLzqXvuR`Qz+ox7E;R$>qrid7Zc=bT@=E} zu|>an&f?p84}@5%F)j<5&wlv&Y?if5BjT-T%1qw~$92A*c%Wo#to5u0;*Hu9pQ2IM zYi}wT@%KP74q)0*DY6=~fi67#MD|sm)6@y)$G;9+)?Sc|e(b$@TLAijW%$m#XS3b<;j;nA3{9lQ{MAI2^0y5Gn&?fZ0H$U{whPu9a$o6T2 z_L!s}Wx=+fNS33O_2e*Nh5gD60sW%;pO>#Z3%Tw3AoT6q;g8O9g1fP^&*uDmA|0p# zOlQfdzc}zVlVQ?+Hqd-ZcJapTp)l8&#I>Ms7JrgUIRx?{b!V5#U8*`-@E|M2l#2ep-v!*!)y$_dU<;xgy{_cGP_b?$j*@U!_MRnptdh$Vpvg14Dsi`9d6m?6fe1HJU z#=@d_kP_K`ue&;EU;UJfwH6wgbbie$?<}@G7ukN`$VBpsgGlR}%4S1CJ%NUA%CRwO zGvszdFnj0hMsmS;qZ+g$9@A1@KJSI=G8%CK+E;##+UVbtE-5R)^%} z*3LS2BR9on*W8j196OUvz;_zLJf3PV$qM1rMs(R0s68tmw0wAYX{jTSl=rCm^v2H@ z{*di2ruEoVrW9FnUzzJqK3*kh)Hsz#BeFq8cf;<=pzZSfpvFPU?f2*(mecs4OeXpa!DMZVBsWWmD0AxGLg+5z2xn55`#47?Y#Hkg8Q@EW%KVK$Jf`SIfjA=O&; zsYSR7oFLbx`|`$e?JdNWwCr?Oa_~LQpB7E^Gf9DtM#uW{-B9_(m6ec~n5<&^ioBZ| zsdn~7u3V2Fz2|dYIYK9Bbq6}qwxT{r){ctPWa87ri@1kt>+6%G)zh`jg9>1?le@Y! zP|q4bhKldPlp^dokxXIbm?X}GVT59Rp&R&_FPcw2aop-s7?~$ zE!D}Bv$Tt_n)|y7-|9NsX8-uMo)yhNGR+3uJJRYCxy1DA@OV?bz^d{?x z=h0U7z(;wO!zCa@h4aHt#r`0Uw$Q zWEY&~imq`-G{Rlt*t|t_9uJhRgIHYMRPVeLpJ~fYuVZwbk__8Woz&S#XjS~$k*+D; z5aO2kL~_ivK?+Y`gSDy#z2LrNk5xbV$dZ8}PTrG~Dfw=T72X4D5y5;SgDMiYEh8`d z<5(((rB<)t=#b22QSCIroE>~iiy}JOG=j#3`1rWjMiAS)J`nFmD-u3_J+Ln2_$9A~ z-rf>8_SnX`Od@!=WHh~Lw)jWQBW$!N2z~v^&WQ?I;rBhZX`!#Ihj!|W^QLOEE<+^l zuKiIfL&ybL)6jqNWL&VRp+N@vlCv%h&hCCu(Ocg@ug;WJ+aj2oJ6nft4~D$al%M_L zbCrlf6nnnO8KAOIv5=iCCB^C7dju2x{CB7wSjXtqpUIQlTqGp@O3-y9o%Wa)+#fz3 zgUP(zEz}8#-dK!_OBwahbn@$&D+&}c@HFAKAOcTvsTT)*~ z_lfhT0ibz9c&L(J#r&e1@kQ&<)V;e+4|r=I@Uu3U2lfvuR!dX=PG@wX(6l94FdH_0 zbd-weaw5y6OAVJO8&;Q>)fFxOaGu%oSCV((4=SpZ*w~wBM&`W2?(o9i`a2~K`1gOx zi9@#Ge@dIrp=T9tT3-wkvgg;GoE#ssyskrc5Uw9wuPqWuHSV_H`o?;(kbLTHs+oRl zG$t9Uwi99K)Y5HyOUoQf_F?8&*c;M1f>f)K>Zp*ctSkuITM2Ka`F3+v^drq;+X4ON z8jV^H$vGHqe>{j0_%UmyZn*vjAze+5KSJ&4WrZRk4DOMO62f+OtAm0s``vKJd;09=v4V{3{W4RrH~eeQAa|VNB4oYP z>nyhmzd6SQU6@`{p6($FLqoj_JM9Z!;v6bx0B|S*8{4SL5!em7Lx`x z(slEgrJv(6fv5&?C_pHTh}32IEs%Sd&iJ`;)Yi4?9G~IPA551okN=quc%qxo2-z53_Nt1T?ah}7$Tu%r+u?_7fK*$4MlT^-p8`fiI$%STfJ0q@u2(0Q z`mE!jAErUs)s?$XJOOc#fSxXDyM>TF5#Ez$GCn@WbwHEKtas>rJZ%zz^~cgK!DagX zc!5RxX&hO@)~wdqVw*ra^EbabYO>X4Im8zfoF?KV`CK9$TezPzhvU0*eh4uX zK&2yu@P>ZZAR})0t!q5WfBah*9s+tN>w3K~S>|e+sT8z;d1a=^+piqd$%fN(in94F z2HtaGZ?5m zhMJn*^{v-ubEqH+gge!{dZr_3wowRDa%gZV>Ka}6NFA^$adb`Uhx-d^2Zk1OB$QMH zg-H1k*!2q+&Ckwk>I)a$Un%}5ySHc{NDw z%_uNg4(Ec6>rcj1_1SR2AKex6?vT>rgxf-uWYil%8uM%>$D6L1_2$)mta0o5`6TF% zm=vgK1OeN2Kv0c|`M%aJ|21my_BQ_E;s%)nCaI$r&mm*AkNC%3d>o9>i?4c!X0BwX ziTVXDvYO!~+_yVdq8O$8d~rHmCKku(-dLLeF@{vf*aTm`Q!mdPE+#&D6ygn%vPkSF{NOR%gCJsAJrjn+mPu zvflYcE%)F9Df0Y2eWF+`P(Z|xZSThy&)qTZ&7TEtBY+Y1z}a4Vmh4+Etz{$f&GdL_ zmcNd&rl}P8p<1x~W)_{R5XBS>y5RF?dxUC|{(Us@AA*UeenvgHuVDWTzsqOscIIt( zhew(pTgZ`-$#FDF>TuV<1Gf)g@ErF z&TDoBn=K$BP~%TW{OXI734mxW_h;EEn%mG8$_3{V1k%;!FGqL$IUEmR-eu^9s|9XR z+l|FRJHNw_>Ih`^&2wBlC9=|WDIftV_@=kk7tw&-6a23p!A`)ux;`8y$7`vPHpCKP zSIWTVaMpN{ft;oade~2UUJ&9X?dgPU?t>Ug@f824AX96ygs+wpM-Sh zNp5iwua)gQeOmM!x^x#oj+#?yVR2DKPw(Xb_9|D+g~ianyj@^2#t6A`j~Jl3K0*gWJ37^`aOV zQY7@XVxgh(QW}G9FN#@Ir|@oPa(T<^P$6Eskt=Lw{{j&HFF@As&At*sa)X?b-Cg#6 z2AIm7DFX}o-9Zw5Uim=LKq3jnjJyw5fL=LjDG+*S15Y3vM-?XonbaV)LAWN(fhI(L z9vT?X&F-B6H)DXHgbIPobUV}yzw!4kz{dw^8EC+_K_?s#l~OqaeZ41>S%vN7M{;Xx zh>`|5b%a*bZ8Oee<+-H7%9_d@{(52cS8!-(DE@J<7JBhR$8Xn@v0p<(m2?jT&K&S+ zit^Eb9xbQ4_hAI|xzCQx&N7MNaAC9(`Ud)}OW}$dHEek({)*O8>Zaha@=Pe@!<8;Kj5u5mp7Ee}@2*5N&Y{p2 zQfbHyXv}?TcF$rV>3?{0U9E$RXQ^nkqF>!%&G!i_tQSlcEj`&KQW{EzE) zO9GCLj;RJ2S3N=~pyuL8Mh0>24*ZrJJQ9qNH?(bR!^LDgx5dAgM@q_vTz{`@GNlp7TB5Ip_b! z{|_01F%sFB>O(vLX9Jk{)aC=?gA(X*4@JerJD}D;qW9rQIdQ&7FRUh)bsDlR&E=M5%oJ6qbMfj5QBpc^s#aFt-cDkMA8F6;qS|*6#H4TxBLO9+5 zFmJs0_W5+%DEq_;`kBBnm(zLkeRrlxN?hC(p1oCmp5@60+j|>))^an2IC{o-I%w*9 z{ss+{)b;xLhS=RB=*NxbCQgBS#|CkUgDxAvtKOcqC?qXe+RH6JoANyuJPg!jKX^m)s1)t{!bc*%!kyXn!9!|J`Hd?*7I z&LV+s9HXS9Q-M-^9jp&LGE@t02@0|`vMmI6cfep+b4jm)5CwdIt67ki*dB<1P9v>)6|hV$TXxuBJg%VXfdMFVS%y7GKb(gl_OL7ftiXgk->Srw8fOyMLj_q|8~ z_FF1Z)b<3_hd>aKDc#s`$eQ@_X1H^7y+6tctM(gX7hE+uh|o3yF!*6|uSQH#v`xp4 zCt$P$ttP#oQ{!b&a7i#ufV?o2$Vd)h3zgIYd5-`Yj8Y! zLy*hPy75M9nU%}Ut5;>Jb}c78$6SYp_DXl=x%6%kMRmj=u0bm^dpl<)W1Qwu1p<~A z$;{Vl99Jz}UAf$N;%v^s?sn z`cJIv-@xuiVWpHfXZe`^5$hk#=ANiG=7Gvl4g^E1U9Wt;7n+vo!!ZG>@^8h2b(c6v zG3rJiKVnhTtZsit0Al7XIWD(o^F2Zy?O$koeB!vYn2`}c?5y(Ak3?P^Uf}Vsl(QxxH9T1^ct0KCG<4 zrp?{Qy!e7BYra_DXwzmAJrIJ4Zk_m=ia@Zj+%u|$lNlk3xJpEHrU!v_VBL5mj-hJl z@~ybo82fU+SEpQsK>5{H3kp|Zikofa4*S8BC~#X5YcC57x9mg<5)ELj5tMc!3r$Fw zv`8MJn*@-( zot^|*>5vE-ao0E^KaQ{y0N^L9D>_{eJZsvwokg#hnF#V;}3x^=5H zS5#EgaRlqZ=G1aZAxE>~;|wkGyV5*~uo1#)1tP>}Rr~H0i%0I>=9^n!z9c?GaWLrj zEe4b(Px)rZ&TdW^vXs7k=?>40Sags=r6>ZrvVg=7A70FsR7uafI6HgtC|Udk)o_D} zDxGwRl!UmPh=|A=UHUWXsC^!kNV(ZnKO7O5-12d`S*k_!>)gLbJ^Vpspm1fu1S=hM ziQIK){bDxANfnZj`Wr2J%5gcbZ_zms+=5_y!=(A0YjC8jLU0lA$?8#WSFPIz|Ig8tB8q8`opqT z<(HGm9*I`Ey9F@X$gfgTiV#l`FSCT{21_hWTnGvdW+XK~eBdKvIP#F%!O5wFwJq|E_coA611EpR0i~Ed3IBo7`YUA9%oRQ`kle7NE7Cz~dF1avRpo`R@^ z6Nkga#ObZ@r~_qqD|zuwoRNJ5_~i-?OCYDY_3iUJ@PlV@*XcrU-2Y3ZU7ZVx^wrgW z6;X0@i~vP1=*rZ|p#SnIfwxc~^qu`$F!x6_ZKFYsxH}~i5O`7dwqx4;EqXxzg6qHF zE;OKzpwSbPe4`|0nS9XgzB*Z&{Admt3>qSk{4TGZkbd$+5WRo9K!B=Xv6r@X)U0n} zgJc6P(rr47MB$DeEUfK%ED<2X8$q5ocqj&ZnoRQcK80S+-$#tZq!hYaZ+YM%roA}m z2KQiknkeZKO6gQObzw|yaXL%8+tNKI*fwZL1ZcuOqX9RT)o^EPFM&YHsd%5qR)ToxnS?b zBKW}*^y*3LbC~pH`%3@fwur=F+;OKvJr5eq*Y*E?%(DycO_7P4d&N%X&+GP!opLIN z*W_~b4`S@k;P%87UabP5;Oitay>EssAMUSBS}5RE$!KK{O2;ufoLJoGsclxnlV3zO z9g5h^hOY5x4QC8o!|<#8hMhS@n&A)O zSlW0^4VMqrB^Lq%vWU?GDTaGZ>#NY-2*SN}&qpGXRlQND5OTu^Y?C#a# ze~Cf%jukjo_rOJUSG{eTc}apa!*u2nzBA^d0#33o;^_LBp)^Ut*k<}0_>3DjF@gnl zQf>LJ5nMOO8gx_LXfA?h3;L@4U^BHobc6!)v*_36UfNEnD$!t-7dd4L07ZHEU;FZEB%^`~c=H1v+N=Pl$+1DS1SyW-OgPy+Y=c zI}+RamG-$V1|x+NiqalnjAWzh+$>+8KZJQocQY03|86*wP~UaJGls?tZKy_9fViex zVdd&%@4h6PAaUK7z)BrVx|Hp6O@A2Y#@TbO}E@n^fPyJFy&0G7( zF(12d?>w)DPbA<|7af+zO=kJ5tqmTApYBr^dMFpZMIw>cm&p>o?BImxur!`i6>Q*U z61IJ%+Z-@k7~r|JKqL{+xAVE}7_V!#B-$COpWpCRP#_0)N72@gP)Hwv<&v)V6t63b z^xFd;O?CBS;KxN7Izg&9c#r!ci4gaE)e0zH#Mzrk|L^oD&cu$jWx>{%v~ZMY6piM+K+tvyu1VJtbDkI-2%#M z4r{P^O@7*fz)gd%{~hCn-q(MANO3FtYLR22)nIy{&ps(7HnjT3Q=7{$c5e}#f*t@l z2toNZGchyGkwlKJo%TV2NQ|t$e#i^ALkhX=Y|HE8$lMuBaYE(|(EiI35Zb9$R#v84 zn!*M`--V-j?7jK+H^ptsG}w&Y={`6guOY__gcTL3yc>G}T0u4d!fT?r0V+x&!M(uC z)ekFBQ*$P5J$t|(1);2+Zfh3>9Stp<);TOtwA=(#!90Y)+^zSkR zB1hx^+`-{=Z>VnZ6B9#ObpgE!i$qa6XE8BA1w?Yi{lXo({(Jc3?@K2A1(gWf%(a6^ zUtsdp3~7c|)${E&KzSqH$srGw00$L(lPwnj3d;0!_b&RSHO*h?b4M{szbD|_X0BXI zjt^pD=W6IL#>vb(g55MeO>X!bYxS$?It{+57On*ub#--r-J7o=E{lP4 zJLG~|EQ37IRdZ zyB^sbqN5J7))u5Iqf2o{(e$=cca(NjO0M5s@rq%@B$dbG0%gX7btMY_r43US-Pl0G z{SFpg8hj%MMdLzOuBFIX+cGOvd3jdT!9r0Ur=iiPf1<`3?^^u4!-G{eckeMzogp1c zy?4k18VX82woVi(IRV}?O*AM7Hy~BVnvhYe@>(4Z>ZwImRY-PMrMq%^_+^?^4SzV@ zwbGd0bNA{jRUqxky7=ys)LDhvE$_w<2SZ=+6ovoF{jo<%m{;xju1q99@E~&8o{|-9 zhlZIS+^YycL@4D4U2{~WgBa=WIN~$cg5$E|bQ%^!jUlDO-4Y0^{zHY0{!+u~PU7hv z^AWw#h2jK{AvF%Il_3Xl;C8waMRR86iiYXBy_tC53-c!5@Nr;x2(*mu*Sz|r;q!S4 z4$8Gne0rSQM(;v;@+T_3e=y7kcfLrt_6diMozY|;2Yf{)me&PN92yW|m8{JvWIWqT z$Yws|JQ0ReO0D&La;pM8#$o6LqJ{8a6((<&+>fnV|2-=>MzpcszapQjEhynSssJ>u zhEAPt$56$NrN(%kUaWCbm<8I@q~8=qPobs7sBUkQWdG@fduguB_2zI^x#!QH1NfFq z?d_)!iezit)~S^T^H=fKQ)JnoErJ-XWk>TL?mf^8MY;ws6W;N!F6J&4CU-DFLcdY( z7~`q-)&keTqZO?CrZry#e(Mv3?hF78w>`bm8ae~QAdb48RFwy0oF78r_$)B<)&KhT zh@0PPr<{u?Fu!B+{bw##lpr(?&y$gCOm0zJ1ZU5hqf^V@ytiyo%SZ@s4i3R*6 zu54NRwXvHtj&haAKarJpTV#NvEUB%$r5e!rKT9@ms*Ug3U7FmUA)WTQqZNw94$Ti2 zX*sRWGA_;_Al+srVO@8a82#^%D3tu$y=&N0nYA(#@ZAhUi}>JbCC}!RSm$Jr;YP!@ zE*Y(ZPfYwyicckCV%Nv4_+Hiix>K(HDzky{@^Xk{=88=H&AW;@LEo)IKgC=ft*^0^ zci=|}V6`$^C1+=>$0Vtb5K1;}ub26#mYF5JI*SNF`#{@uKkj$X`70U#0$Vw-IhJ?( ziU@gn7vJ+gf9DA>hO7N=hNBv%1}7|BHa`iM&a^nO8MYgvokrQf{vYH+(g3?k74#a1 zo`L{Mz(PY9BWFHhql7QL^ysm3bs4nj5e)!b#Y$tGsp_&U7+qREG#=Qjb=FE4=uFpO ztsAu*yB`!4l?nX)>aW1P)x~NA-VF;2>&b$FpRhdA`rxXM-q~SfByZVH2L8e_&+Jxb zS_j^V6LiVegsU@oYA^+xHHfbo???tv*3~lIoGmCa_=ih^rY=6u{Z0N%*^+BJa+SeK&5MuPEKgjv?7^Z#?3Pl?6%TQMao-Q~SENFU`;WNa~S%O1of8iN8vM z%3$SUhg^ao0d9TnyU}`7xO>A|>l#6w?IC`{YI8WiNSmOjEB@u68LNAr?g4Mm#LS5B zFlwZwr9sYoHy|pLjZ8}E;-Qi?5laQFYBO!?e2qOpbSrm^^(H$7kLK$Fdkynr>{b}S ziJh5-awxvfOvW`+phTl(;Q+iN3NzKOmBfAIhn@iT-`uAhOcFshKUpKy2rG4vJcF;T z*ww3_932<=8L`NtcjS(n_gpqNELWU(@A#j-2En1W2E;|GS>G#NJb;*0Uo!?}s|GYD z==d7)&2Q7y>HL;o?hl(SF21WiqPm`^*9eVqQW6pP8OJCbM?5K(cUL~Um(cmn9evjR z8g0bo&jlV-(NfkjyOoNDN=vbK$k8|k{YF+S0Gzu$-REBuVevIfjex|YJEJvGTrD99 zkL?!`x0G!T$K74yde^~G5b;+^M)PJ-OE5+4NB0a(93!7hf~e^i*v%CwREmbgaHkbv zxukm3t$fVpz=%5yTDqtAz66qRJbLfAI+^{z+A$vinePph&6akp`lg7!t2{uHjg!82CiPB4?_6+CDyXAG{)0(`UE zG9YQEOO_KpqV>2Hk(t`*-6zy``m%3H2JEcY0Ku13nd=~}jYoB#$OIP67AI2(FSgh% zkz)zel@&R6>H_}Zm)`{Vj}SNp3&FJIMCrNgBf3VZfcVGasFJ)y>0jkcI+n@9bJGJ#ghR@%%t_iuFn_kCSbh z=%}JXD8y^amznaJ$f~M322*S_-x+S7l+h8_VAK+xU*df_q7lL7V1B3RqA$C}XZx{V#cWo!nMTcEenW{H zHgB0vJ!s|G_ZIRdN**)M`3Cyb0_`m>5+28wAKjv@;(KHZFZp`Y z+4YE9inXGHJg!boh3v_4@|-?c=dONk-OR(C4?CZhSvC8DQ;+{;yX;fdx449Qaql$s z+(jLwj})L0K*IF_tCG)kJI0#yNcxPhcc0AXkmgzRH5ERMc`A8#QA^3ZTc7XCd3iYW zvZRLzdq!Uy21D~{Vx@6rsbQ`qxW-5(s+z8B(9AaTJ&vK3xmuzakjIj*dnXf*=pGqo z5afqmyP0hVIBmhqkkT?vlt<@IK=+;HwZ@JvKc`3C%l9&elA_|f37 zut&Cu?`xajyz-jfW+o;%{#vZ1<$&eHc*6|iZLSNL5lW8B%wi(ebW{Z)vxsfFv4gz_ zoZLHe`%T3pvXVMGRnbclr}|#;Go?cn!?xXr~lJk`R9k=I_3qU-Hx3B zct+S2`8W%dy4<5&)gXvs=CqM*^1ZMQn=OL5lD@k-Dk=I(R@SIKctrN$DGEExl?w!y zwu5F`6l`sMU0WJER8Ds^`{H*iqmtVAS}DHyAW%nE3r&fMN+kCl;y>*5CKuxFR8Et! z0ps1($~VWtC9C{d14%?H;r-ex0R#44gUe-|_bq6$=9b zE;Iw9e%$}dm&1B`V_{_1O>iSfesSlJB~@_~`n&T=_se%(tNNHfUqTO?0;gMYk3E|l zd-~<;tyB3rFLA03xIE|oGF{+*iM|8)I`;#uD&R7aP_eGOhdxFVTW|Ur1q1leHAKK| zRfvQ~Ke5)A%+wYi^IC-Tj#+!Wl4jRe+BSrt3IKTJ3ZAl! zNq>)&$#^eL!!6IE)rR;`8?%0^wb1&r#?aTNU&p1_^!vr*8)62XZab~3Z#h>6n1WG{ zKi^1CsZH#Y!BQ!)!X0FncaYUJzP(+NsZSqz!+-@&>JrabpQZ5%M`1C`@}g~*wfQb3 ziAdd45Pdo7{fO78EnjVKEM0uz1vM-(M=IWe{3Nb>umeo&Fu#4Tjdskt-^uUO2CM>E$ zH%1M5@?d#e9$R>M`+>G>ylRnQ((a3cW8-6KHj&-agDIieW%H^9W*w!SRj78X!f``> zhCM4dGBo9&pKwj-!bQOFl*|(d`{BrLOku2euyn0kO^m1T(Wm=J#gZS@_Gm2jT>Gh|rKRcNeIY#qJ>!MSz3pm> zqw-Ix=K3L`GNnghAA9H3Z7o{dA<`$Ht$fX*XklP&)S{>rwzp=$9?@65bxNasOB?mR zfs_wL*?BF^_tH?6b;@5?+|xkT1bH^M1j5^9aS2own(?bL_shmG_4g5sqIzr z?$54_hU}onuwezIX$U;=r?e?e4ZQ3dCdNa9kuNxKXnW?Ve)7;}-33v4y3LxJO7me} zl3Q^|f$kf-lAxl!O}=42nx46kjHSM-&G_`_N+F$aL!-14hSYuRzim}( zV%lFSF`@PC-e9SQ?Ev3F48IL2r^#!)lU}Q}d?8=WiWrDah2ZbJt9~V~c1Fp$e1Jwd z*M{3+YfFsdlUVjs+)~CSwjwq(4;RaZqY9~@9$?LGvT&`c>g>69r13JvDe|4o#2t#x zpEc%_AnKRvxf#u7J^4tZYvKDva-D(V-06;#BHrCcK2XQXTTO5$JU?XK;}%F0yi$tdU*mn~<|JhHV#KpqfslLMyIf_U69JCRB=V*iAOmJ6*L*H_g*>Mt}Yk8#FTP?>4RD?ol(Z z$ci*8mGP5wP!2#t<6{?Xeoagm!^kok+P&;Ubob$Pp)(yW8$59kz4Nf-*g5uEAJPmU zS%?E}F7&5-hql25Su-au)X?jm-kf3`YB@ zu>ihH_~Ib+0UEu)q>pcG%^qq`<2r9TeWF*e;H9i?BWrB-U$2anTCELDZv?l+TQXe4 zTnU0uM6HWrF3d_V#J7UON;d4d&vRL|W-Y=t<U7hg}+%t#hz$sOBs(CFQ{u(EtFF zUkWypC!i%Ysg|2>pSUYcPI$CV;u~u#Zw|ALFd1vIvWZEiri0#d?NMzuC=(^Zmz8WL zzi9$L%{{hp924(8BO(I{o@2hvEiE?-q!J&G!+njrtfbzj1m8tX%&^pGTCO>Z)(v!E zy;)nnk(%#TbQV$+B1+RUuTHnNBY8z3QBRxNQqF*ET2(wm-i+!XrjydbqKaT`V1OH3 z5Q6TqFMM5g3C)0{hmvpy26c;%#1q_J-N-jDg^J-xgFDaXcw5t)ke z>tjPdp1$N(!Yvt?`;TUO+i1!Uf&zcLSUnprcxRxT8+7J~l6bN-j+H}80Hdzb;ABaM z%= zc(;NbCXx5Bbo`odyMhrwmA_#`Wl?85O!B^}DX0GWC6KS$WQn%E=w)LME;NF zwgB3TzE{qKso?2l1(%YIOS1QO0TmEvrF_Xtqd>0X*HaF?f@KT%K>ouEfju)azFczi zmvb&$2t+>+E)_gpNK!H+%GW+Av6Gn#4>N+VlEq$AQ-V)8(f+Q<#d1PdX0h=jE|{F& zo@Xmh)dazJ!o0qpMrWg^JPV<|`obsF(7}9;V0n2Z=-8->s+*;RB$qODz5E~BiJiIm>c~hZ$K_mldivY{F8Ggc zGT1JaKhtVx#Afwg|8USX?LGge&j%4=_am~-?dI+j7)cm$qi1OK9-)ViRGAkg9(3!} z$1gb~mfy(Nfyse8IYcD9A1Tt?ye0Vl>rOumVCgX&Vl=)edqbiQ{(m3Whh^j)SM_!F zI2-x!AUyw9<$U%08flKW=^+Va}Lb9&+row}JAFCT$nfA*! zHj6p3wpvT^2zHsOhEmz~RZ^5w@&|G;&%WW5AeY0_Dre?Tkho5mZf|(ru~|AoX5Gwq z2(cL%c_-#`B4t#pJ=Vjgtwqbuc#0oiufkuq z|7@Y>Ind<3QlQ^e3Z|TTO#Ey^K zW`apBO4bcRHf5o6l>$?Xv)BYuV3>5qw6*KdsF2~*axj6Tl;=sT@Ju&F(Fn4&Cgf5+ zFXzdFd3gYL4VRpGW3dYzK;;Y%B^7SWbjTTF%{AQXMB4R~hEuPO&a>?m2Ut4}a(-Xm z+NWbnQmkk9s<~Eui+*y-jD}{DFB?xOePqdfdl^ zSEt*>P0bx`ukU@ZQbPZ#%G6r0CR6k3D0wRr6x?ZdTa7*zC@CyPZ%Jf$>jQ|86ArUPYOW!b-9Pz0C~_|C8T!T4-b>lW2Nyl)RCXvwD=}$}$7DP=IC>8{-XM zrw{op71Hy_fVvH5RR#4h>-fusnm72Nn?GMj!LefTGkPwT9tCqLu>d?g?xaGBmWBWZ z-t5mhpQnn2cOLZ(7d)LmHMcvav}am7Fbd}rft$ZC(sYb%gtkuf_K%cUanpT=9wsXB zVFniJDD)*sW0ER)$_%Fy-#FS#}Y?@f?-=Jr?rES4Z)>x6+}{P}^LbsfdAN$K&&63&}B z(}+zKUKLYhp>xUx-?p*_r$(x+scNVR6L#UJ)#BZKa!iw6vDMjQQezlE$z=m;v(j&# zhxusR@7fEJDP)vs!WW-l>*0l_0gdrd^BhMhxs>Mi?7^}w&V-U=$q2}@Ra10?iDy^c z6S?|PEB2dGBs;?=f}8_Ann^pg-qH^rJ`Ar@B(A}3c$&ZIsc57ksHBSVm(969iWzzG zV{<2+p3_319CvSyH=Y#_Rv;K7-`VI5+S-3ncKZ%K4l$ zkdJe#tNYs6+LJ_RLvOeC%(Y0M>fGJXv+G3YAvy&0?MOXGI3X)(mW)Gl0-^(xHaG46b;5p)CuxxR0{iTY z-aIR8F>#(`O#?J^SB^1cOTN~F_N6OEl$6ws@SKVrR~ha50hfJymr?5cB~{fhO->Pi z-(8h}gJL2h@lkq^j|%huHT{V<^~;VYt#4UXdE!;zJ@p2pQAs=o<1dQ zUdGMmP74lgmC_TiIpBI#{~7rBU5ovNSpD-u{=!CH9Lza`kTuD>x;}sLDXtIZ8w~_n z)zs9a+s>(4)^axcUPK`hKuBh>HnHVH_o2feRdNt|!>e&+j=MWA6wU<(t*&>YC6l@` zbWq#}2dA;Q?&;+meEy7tT}(;Lhy_UVYswG3CFs)Xje1j`@4CjW&kh;DqlHRP{e{d` zz8rpcclY$z7=3^s_z5y%HKoY?l#U$HMC%%KRe6mEbPWiV`y`7e;f zzWhHy5^i$+c`01?Xgu0I8vO4{F2z1u9W!TG#*wL<5_t zB%V&d7*Gtsk7vEu($Z4Q$7!#iDnn|4p)=9Ks=%yE5uElq&i1>eu7)K-Ihh-k@%)ZfeU)TXT8$F~-f41aqY&6;Zj9oo^Cb=d zVkp(GBPVZiYxQV2C0t^1eSlzBVs9f@agwJi5{__Eq0&KpZ~U(d%?$5j{mjajw()%K zey^WBgn8|Z!aL92!YUiVW1RgL#-bG35^6Xf<*>Z7msHCpVlKbD&~(%wL#|F7taQrX z4tMlEX6PjAP&Ml4t`1;I{WxsqkLdL{5(|3h4zk=AYWpEZW?oNfo-XL+EnB0BwtnV7 z-Os?xEEDi+g}@LDE?{*}s&&r8?u+5s3)WuFir@JMe8^HuO>kXhYo2~>woTZl)hxLs zR6SjZu!36jd%+ALye~@82<+-Z_($R_GP@t0ytv-q*j1rs&Sp5EK3KNa-EYpsWHWGd z%ICICgHGv>SCO5Inbc$YN1jEzLXQUbsiB|mTijJRRr+QZ=jDKbaW+HtY{pcFf#KrF z<2vuE?P*s>haX-M`=70*2a@v1on}u!-^0nP%^X|f5|+v*ZT79EmTl(f622ihBl)Ah zq;>*t3~A}@Etv;%4&50xQ7p5aAlt|V6k!r>cmU;~{pjiKs{I|mn+sM+<1OJSK>5#y zEfEk~vFv1O?D!u&Q!XwM$=>Eo+|ilfbVnlMw#v#xmHp>YMcvr5eCi2h^CaoX5|Kvh zv&5x|D>Yz)C8}C6&C`_~jiiSXV%b4DZnd`jnUH{jf<|<^r;4|8@o_?Dyr5Nc)Yb>^ zeMGVNDYvt{H-aAnkIq>nK?;aZ*#NBkMkLkIPMbm4%asPLH{trFPhS9qlTp!}hlH58 zc+T=@<<__w36D{xru~w`+GJan?v7K&a1XEohTXGMrPD~dYrB7VUz?7Q5gZVyM$L+U zP?LfGnQ;#`V_;$%TQkG~1Ii91OILPzhAw zZ8N$^w}-5P_0RN8o+cC=KOUYW?y82wV|PGU&*QWvEhJ>csF9c1wAcT^EK7sS&i!^m zwPGU8aHVw7`nC+7$R~vbu~7A^*GK&IHZt&%2T?~;&CrdK`_QY#)Fve+CM#(xSu%R# zl9yS<+9j9W1vbZpJv*C?L7fi}pg3sRIJXUnsjzbB`tEEQXiY*Qshk($&ED{2NR_x* z%@{N)-%E5RPOQM7X&yF_0ZdWcvx0KaX@Q5T1}ji#f>k6`e_ENTm_Z3WM^m3qw)PaM ziRExUv9_(Y99F`lrr|aV-9nSC^_SwE{RLc7!_N67O?w;A_xkJCuX9N|%_BpkWA86b zhucLq_7tR0(iJ26?z?LXZ_h7&?OjPfMf|U-f`TqZ^(|}c5wZ`~Q$J<*VLe-WnUisC z=djzoBMMuB?*J3%q&VzaMTU`P#vSokz@1PGc__v`uk%Lq4XC#03mEyBv`R`!07ONd z+g3r*ly0{WbG3PMlaFhvRii4TV>);)VMBLyma&>z#kPtb+2g{$ZNhTu0W=tQ0Adca zj|s(gr0mOQ%!?Hx@0hnW*eL~n$8Jq%P5$s8XAIw4=v}(7x3^YP$0MYD|32Vfm$RmugQTLD!?4&5B#2~V$7_#Z zp~!VFg^EyRb;}v-b6WwtatMb5++LER}#pTG@S= zTt7;73Hl5szkMp*D|}AEX6e1XI+ev{%-24wrIoq063ckE1rXY7Hi~Qo=$;Uh>>*PZl`I_*vv%U8cZkLl!Amxks0*Gui2TFAIyDNXT(4zABHmy9}7AEUQ9ekW5I>kGR@0)XoB zC>=a7fM|gr1=yreNwCdXUiVM5Q!LLQX4N;`S*Nhsp0Z)HU%avhRY3@MrP&}&>B5lc z_E@M^!F-^f0{A3Oea%|be;5m~ytb1~tgW4?ZPi_{=uqvDF`a!DK@;WLO?Wc&G#d7S2C^lz>I+|368*b0gLSXSppU@`m+QqBJ^hoSmgenC14TT zKTE(OXa6h#ivafdCka?Y7Cu^nuspXM{67J@J}-&lloQ z3Hrkav7Q>-Oe3Moc?iRL8|_Fa-bwr$H;}h7%2#S=_7pb{T=@)S@%%H{qc{qHgeuI5 z!_jL`=7k_R9aAH!8Zg4O5V8qB{$$>`X#Dn(|3*>IY0PUQ6itw}vNwKGpOuzFiZMEe z}JGyqgAWlwF}BqBUQE9E1E2pEG$%4Vr>ZMH&Gtc7@IGdD``ML zeFP&wkFydwcmLQK^7hSozv&tksWgp9MLl2B;I@YWt5{(QhI6MwGkcDdIp2dhDdXt_e5T$=EO)fOZ7JncjS``8 z!w2Gsr5=y2WWiTJ`AQ_rJ4u7?b#N;sl5OOBM(1QE!fG=ZyU-b8WBkjSlRhd*L5LN! z*jd|;pqu#pdy$!{-Z-FZ(;YgfuQ~!%;qQq8z)!zuQ4eHUk;F2JmYUBVA7pnnsn-E_ zEh+BRXVX36fs{uM9^heiiWzUUmkre%5Df@&72;_Q+kTH-E=sYUh&59LSu22l7^c{Se?#dIB<=M zBb1$771^?KMu_1C8pALa5HQDwEq~xHD462BE1_1|QN_Dm#A#O=_sesuitkwLi?f(b zo@`YA`t$-IfVF4H5@j|dyZMx|yR(6^pF?=tB<#*~Sus^Piq#E$7~`tgZ>vl4tneMU zlw*~0^24-S!M5#8A-z3xCO`RvyOW|M&NHzHJ|SsFok2^76qjAv(3FkMgQ`jr)?Y1w zy!sZo3++>%b>=N{h6guXyN(%;t!$N0fr0_$@h~F96SuqGQB>a5?|+^}Dz#~!tG2C` z1K(O{IRsIt$2ZuJx}%hrfq5HK;mlrUauq3vC)L>n;)Q9z^_7NCFOEKI7tA zt~^xl#g>ctFd5rYV5IY(-h%-rZKa$^K!opZG(B_l83~S5GCEalbcgE&9qvaFzklQ% zcfDoycycOka^spcQ<=SqQcjh1%o24qynYjWUM^pg71JqzTP*UwP>HcLNy_k?LcH)p zb#0+Hm~8}2?YKg(d_!?-k5?sY%(*=GyKZ8l=+MCNfTrNMqq(5-spG(>AY}4z zL)08YaEW&DzSZX&+a*9?_Zijaz}J7$bTIDzD@*r}55Z*@U75zmx3`&ml)Suh693fE zW?wfJqF3*Vi`YI?_Hk+B88N(fK#O>D}G!MopODY$ie3L7lpuE{9+LTm3z}&=E7Z^ZU^_71@1P z&F6S0Pb%>7G|$piJ&aMUHAj#W;2-A*&Rph5OiTo|*9blpHTC32tebuv z=GkkUkAe&YB{l?)uy7rf^k=xLNq&}f10$`11F+dR>Ufv$US=Nn0fAIFr{jO7d(j8O z*Y6&N5j&JWDzO@OpWT$=y^BbkZYKqw$SDF6KGW~kz=>Il=~Qk{S55aN_(ibSe^WEx zlMCp~7p8!w)p*^9dMq$m&f*p+JKNdbG(a9-&2DBWR?>*7Zp}}><*5VPR8>Bj<*C zq9hrUOkiy@c+x*{z~iQ26AA#URoOgsWIeOQ2v}J@lbw6?28ymd*;-=jk-r&0Byn~& zDxhu{w8xGc(;3MUES=CaR{?r~v^}(s1(gB6A~ln(&X16v<~fLoy8Qb+IBX0CqXDba z-C1bI%=Dn0Qi$VjRuMLBX91BWMYD%u@c(Oftn;lA6Nh@m9zph?U zz9-Vs{r(&K-wP-V0iXrrp^MF7%o04Mb{&bLfourYE6Fz(q}tK_ z$v%)cq)~4NVOlIE`luBDR^!0k72ms6*pyh;5K4*Si6PnkR_^@gug;KKLW`<#57YK| zWV0irR4ONF!m8AnGK(RMGU8`4OIBI8=;2O3IIxZ>?0&1xV6y+c)(_BBi! zvg1lu+p_}_!K^lP*UkZYmZXY@GgYj>K$le_eEapO>TguhOx^Q?PN=Gugky0%z=FqV z{o7vPJkR{n0gQqlPQq^_%`HBhz30%t9h3$}WugIq%PX*q?<^ESL=dtfHrG=)SCUkj z{WVN_dpDnH6`GCH1cgZTZkuO$k7pHtwecMHv>&BUiDq#5vzUv)+#9DrC5p741DZE746As;<7g z@XiA=OcZlmwF0dB=ZcJ0#u-Uix6QItOH`53`AT85eby$^(>skGZewY;bFm_{aNnyh zy!znS%t~wJxA*U^7Z{BhHAgY49a<N!SS6OCLztMP9vnT} zf!*6lx=wg7ipwsf(0n-i2c#+h(@Vmw`xb)9KD`A44SDY$Ej-R*G=_71`Qj2JMd_si zP||)k-FX}c-KEXG7GjnAW@Rmgw(E2h1|R}xKhaVryq222K!uU_NT2+ou;dhmwnAcD zJ!H>9ptqKLAgWLbejuLvK^$ovdAr3kL$J28+6X% z_l!!#yQx&JoyqRtiC19P>Pkgf{Oc*FZoW#h?<*E)`GOZK87#?DX7se;00X0D@MAjv zd;hv`nMJ9mr(bWTOmOQ3r!}pdySt#eE>n|A)}@*+y#zDX4J02>S#MAW^d9~FECif^ z0te40@cT=JJi8joDrp%JjTe0WgGh@qO$HQCHV44dJF}c3JUyKo>Ma_hBN=jvzRilzx>cV~cqtGnD{EYD zHjzMwiMrwX0>TB6RGp&!pa@m*w6uaIaID}jc=Z2N-K(3cJhrieb}#V@EbZ(85w2hN z7G34}11_IQaps*rb{500hfhF2PhD$h`&YUv9UZeEM7hr*5VIhF@0Y?aDw_89(=J>H z&?~B;rJ-qr8-o#e85LFS`No3wQ?RZrJ}x2A6OuGuWhzGI<+F}0;XggY3x^SS>1BJo zpkNyjc4P19DBD+k{$i-Y5`+0HJjl5gJlY~FDVc1UceartZUhoJn~ZYlYkwf}5Ge?+ z*X%2Y>xl>o2z#6P<<`!}gWwB;6Epq2bN*j!uK&|REMn1fVS_Ijm-Xi2Y)Ai^PB zpn6sWiUnK~TH^JZA)|tHy~YqwStRscrv%UIAZY0Z%i{*^+$WHhr7wK&St{4 zLAti5I{6{_OuhU(CLM!LClTQm_oL~g`@;wLyZ$nJ`&Xyge^}>;kZU` z#fJy22fv-`1$qljBP+p_Qp2hlVcTBsH(#qh%O~HsGXK3mJXFiI1e$8`pi)!|ux`0K z@$}>!48Jl?0{{BG^Oj}boGo~a3NkY@*F{$#OErXD$r7YVNlAv^;_gAF4o4bu0PLa3 z0b|>s0+SOH^8|l!`e0UuDyxZ>fIV_#*8&CrEf-hWuO&zqM3kbr_W7Vmc}Tu*-(ok~ zTK36v%>6XRue8!no)>;lUf*Sw$G6~KEcBT> zVtLtosh3zVLcaw68S8EJCT2Z;=1ej?@YH3Fw~$rVr=j^)jmwLWiJG1sPfJTHK0aQ# z+}@%Pn?v5;)%cM5VjztaI8^X%Zf{?J31oWt+BHiTx)Q?k=g+6S*R8*x`r*kv$kKSy z7Jo!M0?m9F2*IX^LRW_^6jBpQ9NKxwePS zUAj~YF5ycnD~)$rpnY5~L#fo-5aYVLB#hk|-j^*h)8NH!>Tudje}N#~aCXxO4N?#e zT)BNa2wYUbwK*`$&;2Cf@^T|nN4(%kc%x=zCL=~lMfpZu^zjJ^(2lPUdV)?QM=N%6 z@=4`BcE`ESrr<2!?l~nTC2$V8m#^PUbo=LO)}?~& zN4x*r2fB*65Ymj!k8{{}?(;K~Z3$m$Ygx<&I53=c^G$L0+&COpX%8GYfN@@J6TWTI z`<6})(zAOs>~alS$z8X`2-Ua{-=O-Rb8`|q>vIR5KEY4OW1HAi04YJ$ zLlsVGidiJ^G10%c)}v(wYe|`9fl*gOel%Eu-o5Rc(chPEa8^b}2Hpu~WO`Z*uI(l~ z@+5Z%^8Wp;vO0f%|1UK)1jS_M&(o1}S~9_vb`R+A-^{$ePTp$3CsB53=_}H=j1_* z3=B%ylwfI*%_!V}PY83>b@RtDNbjENOuLAIzNKIO!sI_dE0YZ2PP4;Ru`s)rK+zcr zl0bxWU_DTZw;l%TWPcG8xV7rRN4X63%dq32qo+pTnYmqRrPg#1D`zlN?oh}A%0C0_ z?$+U>M`a-t8zxrF==m+sz=G#RYX=N7*n(;yq0_QFE%x9$7~0nv*j-lYD6;XE2(+;^TqR<7yV?)6yG1B5)=^lB~$>WLne1b7`(j&Lj#~Yhgj?K z3cOQkuqEmrg$H)v?+ zA2pVH!6(?#+FBdb62a9wUmxuVyYAS$UuUMFDegSC0oxn>L?h8b&ak)94@2_N)iKbE zhjHe@#O1_=r;idH7>^`624b(p`Ti+BX;^v*wy5|bJn}#8P#`tL=Cl=lM=S2pBX7^6 zD}C?kBJt^Md4GvPoR7HJgF@JiBCYmyTAEvx*rUX+c?{6Rk_d`@F0ZcB4Vp|fK>gbg zW`*;y*O9 zHEvxMla!=mW&P07(h_uD%8WBJGt$W&Brk$r%!^%aQ;L`0TOe^)K$^y$-8Mz=Iu{xI*vrs7 z>I$UNqDm3~LA6nSP(VP{Xtk$eo&k~Y;ofa&!3|>Qf6P1+@c7Zh)ebIHydQUO)XCXZ zfYv{;E~{csV+9mxENklP)jRpHefhpj92|jLL(YWih`Rc_^OWMyuH-ZUL88q}M+lSb zyQ->tpd=fGG9mK_pL4RzHs~=o;7YYS%6T$S9K`Pf_ zEh^^fJ+E|gb=sVNUs!0LsasxN?l|_I0j{nCbYev7)ZEoHkc+}cTd1ZD0r7_ekfK=T zz=46r+N033s43s2+jhvxHT`AwYsi=`fe=*F!M=QB9Ek87)=GdXyvTt7z}xbt&%M1G zHohF~TdNldPtNrZD+~p&&e^Pi!j*-;hg`Q*`%5R~Cwvp8)&E;ifuun9APIRn;_0uMCw0CrZ z0|UQ@DT|$HkfH2wL*5wK6BHbr z0%OEpv8`3$G>jYyq6-rgCj+c+n6@ZbvbP;qbua*pk5$2&VCI96Apjgj(aY92P7h{X zSB{afu^&4db5}TZL}RUabs8=}w`cb*qe-xeCIB(vjSM+2o&3h%5xZdz188M3x4kl+ z_~nZXJc!90jCmb^1TiV8)@&4!;GpWg^*#;A>gcGWEtTDwXDACL)>~jImzS5nQ4S&% zeaoZOiqOmin7e;8S|T`JvRr=RN6`pn^xHv|8+4~%fn}))BZPVJ_1P&mHs@fjcO{6{r$)3j$zGY=r@OVoniKwLtUn zX6L1HyeeQfzacMK3f{WH3ILP@Goh764v@0gdYZ>#1Ow6Dpr(8C1x=RgjEXau8;NfZm=rleFDWmA?Oq$e6andrA6|tNe@PW?m$68T>OW@ z9T_M>VKR4~N}vG_Y{g*enVg8f;|{byicb61(-ZzESvHmV3ATzC|-s)f&kMuHPIM6VApT@J3~1f zpOozaDQP`i^u4cdBH;O&nwlQJK9R?dN2jLruyzg(JQ$$&PF#artae-(-@Azk9T0Bs zeSGlk2W?4J4qj`3l%!)|fJPS&Qx&pewUwVF5T$i_|BBeBRRL6kZidS~^z|i*<3FV~ zK>wIM{g)^LNfgO%&jvj*QRD;j0qrHiGNOHaB>Xn^_xER@*ufxFt`{zH$paCzqz~<1 z(I-B3;?x9rJAhYcF^5>GE;XFn&n(lw7nYu|1)F*d-Yw6hHweHx5xf_W5Z64}Zxuyr zOn2+4txOUl`rR{IAj zurR}%h^V-@=&$W)r476Kbdp5)6d1Mz#=q9Yd9uNR0?Ya^#$6*WVnLT+}s?R zd;AlgRSY>DW7+jo33dy8C?dyhzJC3Bd%2nr+JJI_Vg5zLpUP(T_j?g4$G3ogZ@KL` z0FflAqCx>cNJmevZx$R&0iM)u5diqJ4Lfi28*air&ZTrTA1dR0k`M@0G7zw>A3Hb{ zEwmi~+D=v}wklw!_tt{ikS0TAk+4KhELWW%9GP{Rp?%q%fbLU#^8r@4vo&7cdLMv9f34+Q$A7^6ff4a!Fk&tb9;&`X+a*3 z!GbA72DjPRVRveMe|-}0D6D-Ecz(Dqy1WWHSA}=yZ-X{3vKT$C-yREt&g58dg_yXe z>eY#34l_yNqA*0g`f*9-!xbTHsvkk<#+nh)lb(O?<+XMjOoZS=45Q%J33`Es?i9p}&RYu zZM*DxFoGZ3kBm^cxbFM>e)qH)C03ZOgOtvT$wb-(w|GK&PJlJJ)}!suVLl`Z8B4cblnZj_j)=m_j+=nZ|@r}hEp-H;w1uYrF7$PjnfFX)QsKsq}9bZ3xLyDZ;- zHOp=E+soY@gHmItv2EXe3`0mrc$M2s65fA#Wrb<#4*;wu2}c&oqi86m$Oj-OcYh!j z#19Yv#Rn?!US0z)ZImkD<-JB07oUO_lHenNC%<2!d~^_w|3^TJ{69yuM}SsEf)4}Z zRh&)1YxNe)6pg!45@3#;DK09~;EV)V&p7i82#%ZL$kEEwTN)|{JPMjovNuO}1sYk; z1Weq3YL!1wRRr`0$W&VBmX!GLB2ls7pF9`1_N(Ui z(Yz1Az!B#oD6U1-Er_ac8#1`c$)l^h?lt*h0+NXG-GCMdjAU|dZ!h9YM_(Ne&+@!n z9Glnr-iw&}zi&n$e@zY<6X3P3YyXNc?mhq-3k($BE8{Pzwd;HVx{;(v-rr$1^O)%m zKX8H6_K#0G{Pzv?(I<40pYpT$0 za`!e6V?U~^fzYAnp{;|(x7R8(zvwZ-TSMC^GKv|SJ-zyG?larnV4<%D@FwmA7x5y%dT=`(n zXg360Z;2Bg&TK(>*>mS~VPR}<6`wkt(u$#v7YN|~aAXzMU#kitc_n^{LffJO79tDtb!b4R19#RR1~r~8g1|!AFXc#h-G0hnOF)!@*8)tb<8QuWs@#vA=m9DcYD%3% zP4&G4WOoVJ%o7)=silFPb6m*BlJQtv1F{Wh%}by)9y~a1+?_rOK7zuH(FOK*YKo;Pe3if2Xt`_#S0m z(P-T+s84Pl&ZfEzgxM&v`s*Md;KC4^F1InK@M*f>RzKLxty5Ls1%X*7O*u!25-@?} zYTeIwU|B<~0ZP57*lL#-74h`1vM7pK-*1|jn4P!{%Nq`r>ViR?<4p0tV`h~j!Bt|t zIKZn0jqIpgk@e?LZ1k80|30M#@ZrE;UnCG@R zUGiT5#@dO@gU?zFgcz#mA(U+8Bn+KTUD z+}$K@3{|@{dw0dZ2wmhqGP;V%q3Vr+oVYF!jRgrS#T;Y=C^rirp9{+g%WMkP@3inC zju0BaX^&LBvVr8lOy9ynZYUAY1MUbtAOQdnrQ92b-sZe@PP)Z_oH*ot>Qy-RUb2c6`8&i$MS#^mK7wwe=MR&GwgHGcy4E3DoZgP~4ngg7vRvYu5uQ z{7_4a2Kob_pclrVw`;J%wRBKh1V+D!4|WSU>7Id}6_|r^e*dI(iRN55-w{{|^$4u-mxX4Os8J3qI51E{qbs1+b1lc8wCLq7RJUwM2HSoxGENyTxl0rBLIDu zV81lv*nI_d1Gu^b_SZYn-@qVt=ITpdW_hW>JU%^L5B`#>*C+9}=X2Y={QTNh7)#UA zn8E6q{GB3Owzt}R@@hM}u z3!ASV>`^j+*=A;Dyal9yF@Xvn=VjM`Ta6zvEzLUD9FL45QPa$1^i+9EF~dDmnYk{t&D#S0^937ZHD{H zFnS{&f}CJ`2mNJ%k0{yy!pOqWn#RALHMpRq)xX$RUtiy#^Y_g-`lf(<^*>Un$G|L1 zN_-fg>|4M~6gc)*4`D!uNs4)1D~vco6Q}?=3$m1|8geUoRL5S0_K975WD{c;{V?~{9jDj|LRl( zJ@~(v-v8CP|9_l{pxzny{MQAB9*vGR7Q+Jzj%JJ?o#{*jVQZC(j!<`E(Zvt7BeI0y zMucNBxumDEQt7V(Ej>W?7t2pne+;^}^A7Kp{EWM@&q=c8Q=tX|Vb;61s3WD}u!VWW zC($!*z}g@OI0gg*Z6xcuHu?~OvH+{t&g8@Eyf_LmF?}&<;=Pm-!Xf=8s`j%X1y*UDG2#CJ?ce3&iUQv^%` zxc9U{n1T6xI&Gt){yz9p!PdF{Y9)sC>8+SzW(Re_{WXFg7kxPmNf@P@Nx|=4>45s3${gVfDNj_BLz)f z^r6NrS68ZBb$dG@=mUTfo`lqTpu1p&1t!Njc>_m|kVvGHveD(TFOssUNq%omBXE;D zvAnqn|GLA(L@N69>DZ$m&t+uVNK@iClgiH!nB0rx9ICwtPq*=Rk9or#(jx8{UIfhs zqz$OQYKUHw0zogqcZWQ<@t=E=k_6E50@_6Sg8;~dda1yMSV-+i#^~3qAw~N* zbalp?O2D^LL}t`?wt#XyR9)0mSQIyum679}8@OE0~0czNP{g zo~3DEeZU$o__=?BK4uaoRXm*S;Ra%qPmyXZvZ3VQ6n4`l4pZ-UcweTYmnQ!@&6woT zemado4)?iH8>Eoc2UHImo1g7mcW>zaM6k3MaD=dkenSugSXaT((I%*61kws_6b2ma z81U?{`f(6a8FAP2Dr&0^563~QjW1APdTE75UC2$Kh;Yv6<`s@b>LL%9K8}KLEjs0snXgA72~Hl-@qekm>3a2cc+hy8OoF1Ge(NsKYflU{CP2JQAG4KsPLA8 zf#zm(od8f%`colIM{)9#xUR6LbTIc-)#bFS&;AJI(!~la-eV#PjC%7|)qQeYo5GZB z_8Ef*14U`T_})tr4=q}N;=YrY*;E=JZm0o@?ZfIi1BRpyv{PPR=;v0Bf;UY7HC9wy zoNYagJYB3jbOTad5J<(xN=jG3TpEYrYRguGi-E=762l)35i;df<5zBJU~UAT0d&!w zrQRNJ{h`Smn0&PmXMwt7fYs&)hdHCgu3frb`b;v11K#C~7-k)+SWh2>TV=0QQ%E37 z)Ra=#E&BKG7e?Lbk*0$HD~~n=G{E5PhQhlyV5SQP!?{le23Z}$P{+LPbXpzd=I;V! z{T=L3)Dez>h+c5sM4MOO-eJ|8&$+{e5ZyFVaHpe$I=slS?b);}h*V(t=>X(!T}P5c zq<|x*iqM8Y^FXonF$eI7;J_5R0*>ZaeHAjZ;jxKsavxjvsYjIfcLR4HWGmgG#+?m! z$Fn@+5YR%a{yKk|d;Q-j_)X(p!oL)JZcI#I5(IdW5g?~f*WA|%Sy}cN+tEq6l6xCx zm4p}o;!MboDRmqJN!{>aw4mQid-R6~`0vp09f%)p1RlRP- z+5KSZq;CZ!J8OgF_f@QtZ)pGJn@y z(bQ024ONlNRktS{mE66}Jf1lJVvqB!;HF9ILc}42{?%M8kL7VtQ9yp-ciDF@wK6co zF+VGR9oC*^P86v8>hF#l0d_PQtb+O8-1-8RIfKQ}_p04Qd*CZsX=2V&36A@uoU8X? z@DS{sc%YSQyWP5HW>8c4o}axp`z`CUAUS>92FvLw*RpGi3YA_@(_TgdsIy0goUTl1 z`&0$Vf4$-A>dK^Aa);q*G^52J5V)fY3#xx`?6Q4$?rYQdcCBvro2R=#`DOg|nx96! zi<*Ca#@o#I1cDzjI5xHoA{&oEa14?~p9Sy}Je>lYJ=(uBRimKrIEgch>=#K*@KT{n zyE`5=;_}VGFH?OJNbXx;Spg+e4WN#(F5%GUU@1*mgajsm)iJS`ZSTq%Iwq{X`hlkO5l? zBDrY5<^W@Zh=QU8kJ{xPG}JPg6#6ghcr8^k`1V94C2KTpErYAzw$(&~mMH?n2gHoX zroLc8l{xqf@dw_;yCg`6Al^Iq{W+X54O%cD_3F_ z4_&<}Lk>QHn!iL5@}HR^GiZE%#vNRu{U5e6J)r6$Y7(N(c3|&$UAAw<$H!wJEM|By z`4A&BeP!X|GP&4&t$v_%AT%x>c*FsSsRZoeBDDiigy?PpR3xxFp9}L}D$1dhaoKKR zs0p_w_F`ir`N;=9USw##O)GnOm%1`Az#?6AKa^=f%D_POlZ8GxcW z|8l;?lc>ADY^{y|Me2+9(BTFc`(p-ZSQOw5K*Y_(t|;8Smsgw{-S7E5IktN8&6_ul z^V#(g+~!nJUmu9NQ{X~_X5$=DtzfuYAoD#O-I=OBwOa8IO!oNc{4s)4w6F5t^b|9{ zdt0L&EogtLGWD2)^X|x`+{cg*eDLg9(u^7e`UClY>h3cwcA7$pBRpf(jQXpA_dpdE z0EFl94^BelL!%0^G?di$Cc?j^o@}+w)}M<#ECK)4DS>C7_&!|aqCu`*bB8)&eo*!v zEHlHxq3Y^4u#Zr47uq;4y|dA6+Wz_35$`-TZ$q{=VlgsOp=HePbGTsttlTN%!7%l` zM>UZ%M}&Bw%S+jdqQlD=whBrPxWSmnGVAoFCT|JGdTgtRTiyM8no)co;QDFsOBBRW z*;ra$zlL0^+Pw4%+XTxcns=X2tZ`aYoKM2ZY1Hjx)!^U>>NqP0=kQvcR8hk$C?lDM zqTavWhs+ai6yye?*sraP*l>0WyK^yR>Wr_Xcyf%qqM_eZsgQ63eB#4(S0rTfX2K(t zM~M-CEoe)4jGJcH&8yDY8G%CrIlaRq0M;D(nf@C9^?$I2j^rSE>c3*8j!qr45cUXn zIbfM?i@CwdO5#(i(lH8Z3;kd;?p$42X7up)KSRA_MNjD$Lw>VZ*m0H7@QNYX2R4VW zRpf`ptq{~taP+~vWWC&S8DGC`@3FWzr`*lma2wIP+%1N<_YD4491ApNW{VI)$(W7$ zf>2V>`?~>ZM|0Z~vVHktvD&T;Q7EnF0tXoAW3ICbf_>eNTJlF9I&~_lSQSYrFt1F+ zJdbmWTOn*Scb*^#5Y%-2@7>34L84V#EWccbX4QwFf#l@mF|D%e5035yyo1*-13E#$ zjKpepa{A)Jmn{D>_uw@zf!yP6$tq(?xcv9ze{mG(jidk1bO!Qoe8zu0w<2K3Nt^h? zgO3&*lb=m{BRlmCF8q$DKiCaX?F}8y_2x}wfvG#w^!wLK z6L;-jh?!=oF3q;&`7_+M8{p3~>-0{M75;(@lLAV`vc6N^&!3e%Rld!6*~i0vn6fqI zc_2OId7fX!0uq+B&a^_LILm@*aWgjYMSLcjoHLkG`-KA5ek9wKfr@Gof*o?Fy%P~^ z=HS~q1a-~LX^*s6*`Pt(rx1mdp!4G&OXcbuX*&i;qo?L~I-^O|#9CGF-NUdd-AQou zEGXOVzzjJf6m#9C8jXb#S;_=`X3-)K9?+Qf6?}p3kAmH%!@f*ENYn@!APoY_{?@m< z{PufM0|+>7-#m>u^aoZtS9>=pVVjgqWu$O_<FDXF`Sc&V#!>_-P#^Bv zu$_B-b*+90=?PmH+Q5fDa!qEukGDQFYhQrK@8UMG`*qyT-3YEO?cgt zwct?&+R!+Gn+;5Kevc(}i)?+Kp zlo4BaehA6RuV&TDHnlC*Pn9+L#UFCz@YZB(n#c7S^Cm%AN`I2zm5*HRIj(chHz!2} zy4}Kz2_|Dx{HAl>hs5^H$xxfy)=k!!JGgM4i@QI%UDo3zqh1-65xZNE+-9u)r?MuQi@y_%p2j`;T-kx#8$hue_BNC$dsWInu@-b&;s|?Lv6)Zjbt=M)pfD z6Qj6+_PEN^!6^c?BIWnN;&!xeanRD{y(t_!$?`s(gjZN;1}V)msyX zlI6n7VZ~2qv z-rky%=k(`Ku5Nq32a)>hY72>=LV*AHq)g+0*(%5FI_-}=zI%@uf{AWRXDCyB;L1hy-~&;#;IFI=|^8# zK;Tc5?^WRb(MbdEq>_Muph0JW z+^ebUh11_U;%8t@tGMUFvCsFGN8R; z=$GkDcH9P%q&&m5==JX0L!JxBw`bX;wxvwzSySvNoQ459V}7yjYhnGjb#Hb?3RhKC zrb@n7uksz2BldR;M6``hc_`4O$P%aeWwl8G)lGQZ^GdO-x zptf7=Y4pOrvz(5RiiEuo#a%l=Ci{Go!=v{tG9}A$c9M{}EMZxv zzS|M5#=)z<@pS$vte*2xSa~VfFJEU}ODE1shKT8nKUHydrR-0UD(xqoeTz zRI4?QPiIPu8()0|kxNHAVsU`|t1NfGucfk+_jb>l&SWoX zg701S4;e*d)LtLsA0GYIowrq0eZEf9tdQ1W(D{X=a=~A}+m|=bsOv|3F{448zrgCP z){i2;zGb8zFXkT) znw?Ekx`#2Z*tn{ajo`F~6b4+S6&S1LX2tz}&tMN7<=wUfpg;!%K&RXI)rus|;@osl_d$oVh0|PpBl30l>>6cw z8_T!t?jNRGTSXK-;(8gkFq~KIuI1NTloqt+(~7!|kYG5M41C*{^@z z%BRaWMlil-@Rn5s--IYsR*$K^YH@0{9L^>f|D_(UW(^z1^=)q!8kF2= zRG!mK{yfwJ-Q9~_*}R-n%1CI?F3oB4i|>9J=wRChqXlpbfVxHs`fn2T-gynawao<| zZ9rvrjDG*zT(AoR_h|pYX2vZ3y{1Y2rKN#veD^Ozo79Ucs;ZP`3Ad*qg(n!}hSR)TJwVMPy0yEMZ+tsZ(;~KU zzm;!`_sn@ijV&P5pSbLP*f-RGXbkojD2(|tw>x`4DM!&RYq|cbSb|~5W!&`OV3!&= zn)<+M5r2XZb3=>e=U*&Lz=i^%s~yg>!vg=S}cAn~g6a2iE~g>$NVm zd>JX^06gt@Wf<=?Jl>Z)hr6&`zL=e~b%jFh`SF05nHLNNMK%Ht)C zy}54-e@d^f4KK9G6zdffw8|asAMcX+z9bWq7owt_Jz!T~NhrYX8fm_giE+>Won$(Q z1RN~saN>u>v7tK`W?FNQQ*Sph zEB-`#QQ;98!Knui$!}|DAj1N??P(wOCTu)M-1c`NFN5DrtuS_>-|m)m9R<3a*a;~^ zw8?R`&^j|`VLsPxq_~m&l*rVL%DZRzo~CsfZM~Xon)GXXe1F%)d4k`id|MfceMEtT zawx$D3#=|{pWmvVoZDRUF8sv72ikvPwisXr@1|@7D$|f(SbI&9`n4Ant~P&qVxOD) zYqoxj<8j^`NTStZjUxGa?rd=!^PlaHT>fKowZl2iSa!M_H{jJE)@8WoVs5DHVe}8k zK>#lXF^}Kgn7vJum5r*KSrTAafJ+x%P7l}odD3TQWzD+Dejc0McT-1QH~+F>e@lj$ z8=z36PVMK`LlKfGE|mfA@wJIiZ*Aexug1IxcC36^X%X?M(V)wfza&#SIV7LLu2!u* z?$Qx8B>iMxKGDwU{1V63l+w`D)p_TIml^lY$5pOI8RONtq?OEM79ZlKHwL;Uh194x zqfPtmHBvRkAh0sNzuaCHqU|Bpy=mlrbrF)*Twbg;{Nh5O6fL~Y2Uq8D11H|%=~d}T zSKQ=rl#jOWwtrFmqg)gjtob1o2jIauf z!)7L!C{o8d-uQUscJj)|L&2AM%X1V;){+nx)KhFt3c$0#WGQRcigihp0Ee_nxtS8{ zMMD=D&D{dZ0zy2nfaiNEf}t!J&mBUK8Eah5(jT|O^0I@e(^M{BJqLf$i$F|!u)5p( zeC06Pb84E}Hg(1AtzFi+FgL$bWA@4Ce$)i1_df>ghI48XdmhIi!o&Fv+*B94=AXLl zSh5<+A3o~0Go5eKxY>s64}@CFM65i1Jop|&U`Jxg@2{445HUB^_VvsBK(-IWpz*q_ zb5%}H8TpyW;ucCN7Y^l}KTPYl`U?{knpZB^Yz7UOogHi1_vtcX4cqsjwQY;#ZLWDA z>^SmjH)VFNnLnYvFC$Ai>wKx*Vu6I)4c|leTAzx>Y-y1KV-aMip5lG^rzOvmmgR-R zuNSq?mIxBpgF0DJYsNAAZT)yXk7UXJ+)8>aRH)FD{);SKp3~SPSh4g>WPa4Wl6~v3 zib?X0kHU|dAoI{1ce=Y{^6a3xB`4lLqgN|WvV>PclH3^lD;IedyFqbfEo1fzpOvug zst9W092^uPxfUzozcZrN+B9KK$e^$3`eK)7_^ERf?-{6@v!fyX_D7`P+$G;9v{E!b zOq-+bewgSt4?`o(uij~JK2fe@vmU(e>UQmo+EwxCw!`}Pho0L8eFTehLmz&A8(6v% z7k-0iltsBD`YP9AReil@v32FQ!-C%$;48u|rzs(!$+2$HYt!t@ogecb_k9;ye@6JE zg6ktuR@wbhJ5vbNzU1jo(OCZa1jhtFY7%={3hl37f;)aPrAR}z13}YpI5y$=NjS9ylt+3Yqr915eF|BDYNbq(N-{JJ6a_?ZL>T+NfU4E}}wl zFn&6V0Qqp&RB3H|maC?odSoy=VJ&^!02GL72-beGsW97Z@YL%!bDbqpX%x-GI7ch@ z1Z~KByGL)+$&PZTB2qwM_@_+FpV1kX?q! zt1%0A+Wr?6yJjN0m9=W)3j9vUI^!JHd;&b5^I%1*yK%&YWhhx}4XWZ(I$N4>pWML4 zA-Bguu@G`k0=>vfUT(7c{f^xJuJcXl%JKtY5HUZed)@ZR>?kvo3iI+;T;h%qxZ+5l zkbnKV4RXc2Wn-b}bu~X%z8qElRiP=-`IBzKEbepp`!Wr#lCKIqf&Z5#zBU8`r*=i{ z&!2-WBM(RQS+}J(0{zL{F?_Zy*;3)!yB5Pbhxo)-G*z>mLTDBaV-wbY8k3;3jn804g15nkLk z5hjC09s1yUuPl`}QzI054I`Q@QM)A4LK{5Q z?+BS}qO&Uae1R#y;dU!hF;{4`mK3i?HSAex2m`fr+=CQECdt9JCJF@^A3!0Qxr{DN z;6P~o5&p!^5v5JW>A*NMGb6CB;jhxnfAOIk3R*m_=tHkvGVTM)&4t1GfbZ2_pxujB zmU#B2lp=%}o;vw^hu&NTs)AF$?eI_MR_}4uCstd6s*buf&mc&#t@+_Q&AZADvorNR z#7M&Jwkx|%7ym7oz}1Y(Y5N#>@ZqV*!ABHKly$DKG*a!kR%6z}=dubBf5#~N!}g(= z`rem92h>P=w6?tVl&@}!uVAS^aZ|-({1xV;#0h1h=S)oENgZFtWS@T)4V!q_5F}_E zDEeGS;v7f!745ot>&#*5md3~pq(w{5&ry)t-_fGYbr9>Sx-~G}vR@HC=jV^TUH#f;iOejl zZ*5`ov3J9O!0nV%%q*xT$uyBIcC{}p+3VQ6(*CDkm-&vv&r4346Fz;&YPG!J%7lhg z#I}09pOd92x1;UhiE`cSmv^it7#Di7W!ZhNJ|9^MADHSbohEeM-*_N+P@{43w9pH$ptUyu_wsd`o86YSc=4 zr$1YCFkRA}na}tqgu^B$G;z}8H?wSShdMN0qhELH;yCoU9@j+m^10h>ktXNXWbr>M zHt&ms1(U=`YY8J5C2GvjA2k0OCK1;7Ec4N((Y9h>M@7l8rtUE*@~1AWp&=nvwlo`` zKG%Nvr}-+oGTZQ)`9P7PKOt3Wf2n=z#=Pz3+)5yNRh?;tg^k2kRtpkT+QuzGpEF5_RgT%=A7uw)j!X3dQF^glh}}; zAS(}L<&u`@l_ayp4k>D>y+^|ts##D^!f5ohxlN}OrBA6L#%dWmTKW64?o35Gv`()Y zEZP^`Y7S$=LMYM(jx>%}_3{#(=dSkg2<`ObPuJ$msRB69MyhK5VCKZo+C)1d<6CmoC3=2cID&BOyg zp3jramr1{#!>ks7(OM%2uNUBn6$T&J} zq{({Mc@g}kK)K|`9Kc9H@4r!%x$NZL(6Ep4|KY6LTO`bKzaiEYU!|QInU@t6O?qH> z3B48OapOWY;#&P~!&c+)`|&Q#!hUF9;>Kn4``gbxbha?}o?C+G&2&|UHn@MH9!spM z)??R5Z*pi8uKLlR6F%p%SW7J^m3*V&fDa*nbDqvwSj4o!>i{R_NgXpyw#0XQT6%IKuG;^~1QzeCq_%~Vg&1nz4 zaB(REe>AJP`O*VZ<)5Fft}5=_kMDNmI!)For^X*fVt*UG)JW>+=BQc_+M%4m^ZYOG z$+Y#BZ$x}h>LqgxQqh&92`MvehfRO3u?r;YwMJbQQ_iBZ=Z&~<_~%j5okc`tpcTD} zd)m8x;j`?6pX#YX2hFhc)KcButjZhn*6m-LTVqJbUbdw`1PF&xu~oj13-THKo*M%V zqfVd9*z*TE=|`1ceU$OfFHq4iIFx=?r0sOI~_%En3)UQ+QXv+=st zt8265e2%LjbTc1~jb@OYFIXlR&{F&TFV&uhL3H4Vl*=j?QsN%?DR79No80I;NV?j` zqa5~UTpU01bB&olbJC=0N7C&Xu|h#M|DM}7b6vB25l8w#K2fol9bUQdeAkq+${5Dx z$kR!3Uo#W?<6f+u6Qt;TbE@l^)>d+8&-e_>@S4D`f71t}xfh6j%WkRe9%q(pd#A|6 zZY5S{uu?83?74Wtrg+hzRC124n<{IZE_K7&gl6xX>QI|bTJgnY_h#FPyMk#4MLFj$ zE;`7EHN3gG)@;;R8GW%-C_d4Tuw;N89ScSi(y=yD@f+vQWhi~(%k*wuWygYn;!wbWm2Cc*EDi1TmSd?FW(_nAZ$34QeA*w0Z;V=09 zk^zG2S8>wRY;*B(l)6nO2QsV6&-SXRZ|18>;Myozv#3e;`CYf{SEF_kJ5ujEr%}7rAVMx6WZ-M#>VOjj!SX$kE zZ6B9Pr&^A|o!uumgj3Wo-j?{V0Z-g;K+#RToMkCLu2e&Eu}+N%uVNFadC$~ zf=P1eOv71EnyqxCE5gnkErj5-shquL+_uBq=Ax>t^H!#U4R0gmp)}J6KE{lxjUYN_ z1Jh6hXPeK0hd`}Iy&yW->~~|-6wG4chE8cL^#=i4?;^#`dS!zq%8hb5e#|i+j9LaI zKW4((1y05ZPvlzVj9nd`Ponj?8r7EFChJxakB1kx~Sw@zMJutxyln~ zcG9;+4dt#fj=tiicVx`J&+qs|q{()S=ynbS~Yo9M%Ty zTm^NAe8%(G@PB(9&H+-AQY0W9i3?wPT{!7DYhJIYzrfHi#R+s%A42}e`qMCR)$)(DOz?ppgeUWk05hs;4|ujRD|NKm?Gfke>p zxE=pB-!M)#Y+(PouZk+3q2>nVVPQC7IGrP1?X)q}J;Y!vxULV7x-TgtOzu|;&=-~| zqa(Re@ck^AbF%eyXQe4D71d$I-6uhwoE*Fa9s!+sDs)3DzA~AWQr*MYMkkG^rp)a} z?_;Q+kCn5lvy6r&hARPgWVrwiidlOhMKmN=1FqKjB~F{52^kUl?dIax|<|t zy1itg=C&$e{{seO?Ahjroc`RHiNKIgoTZh)D}*m(3K?uhXAnhkBc!sdGH#ABSZU6w zku0g(Eb})XH)DR=IT@?6X4|41UtbkPJYGw~NZxePqhYX*E@sD+vcAxDQ_jj$8Wv>| zTOC?YnizE{tD)UV+|v_C4yjXpF@+_`q3BQE77?v*85-ljlUr{=Cq$J8Y2q*${a}Lmq_~=lxK*sjUt1G|LPWF9-})KdiVN8+s=5 zbD}by*cr>!zWh3-M+}2}+gi>-&N57`&J9*$?k*KEUlY5x&_^%s9&O^}w9=)Vs0r0Q ziP0^DC=f%Ev4TKlT-<#Th${)8oOqs@0p_CLFV?cOEEtjf9#tx=GSekDz3+t zDM-sdXrI2#DiXvduv!sfbN?Vth|NEuGm?CMG66sQNyk&Fsi_Hx)Be5HfERSi3Jn+k1TtR}^~#g8ug ziH?~g92(5R$9?ZQ`c6>)cVz1H7J39z#QWp#(3<&K99`5s>R<2u-#X-9$~JL)R7s(K$v96d!85{S1{PPZ1i{SU>^JbYsLMyhU33Cs4)Kj zmjmCX6cI{)qmEhV;5D#wI)%w3dR^#6FhzpW(ms%&P77nXy0XHW=Om~#{#i=%1-e`W z1e?FA+zUWQCYyNH^6xqyP*XybQiFmn+6vtjuQOA)SN!QgO5w8~`w+V}F}{#IG9vEL z0TnRelWC`)@bo4xQ~a~zZm*W0GJtP*ZKwFRqkVUcunEtzp*q(Wa@6ou6kb*Ddu-#M zJ-!|{cMw2+XAqp3D8guvy$ zTNVd?X#7b-Yk}$ic?*-^Hr64|$J@_OUPTeHVuhzn9Tt^P67PyO z@x_auq52LEha;IBvm|-%@2?;!>Dv}7my|7+RjG!`o1z?>9=B=f@%hpGn`1-HG zXPG&E;U+SraR{qn7_S2o$NQ7ika3$DpS zhT46P*Lw5qydD2B>U%flX+xhn_D=IV{NeU^C(rtG)q5g#7sA-DX!k@|K8CP-b-Pkk zNSk6>{1Rg1(x$2q(LaytvF5z^or>|;&5$e>ZKQ*k+&Qx->iDOBG^tb8Z_Y-`(2WZ5 z@hL(0FT3~S_>i0G6=hI*5oBS~)ws1XrgYQ0n=W}xVXO*_rpKRI=n&yf%zK0oL3b}iXk;mMenjWF2zYKOn z$TfHsv&WIL-t*5L2*_SBzh6kx8QB^s`Jtp>XqZ9}ix+ZD-uOza{IP47HUq`8KJN{Z z;u4mvtX3z)OnY3)#xX+&hnMfw3Bn7$NHiD=VZ{EOXP-S>7Z!)2t>f9G$Hjg+X5N zGjfc&L2!PmQhqA934@YxR1PD@z`rgxSZd#1AZcOo4GJ8`gYUTFf|AlL@E8AguWtWQ6Ci2KNg+gS;Xxmu-uU`QEj&1$Xq59ECG( zg+^FVlaSCu8G!wrxh%(z?Z9I|@uFg_1*=eI@(IYD?1J1$NY{~oatp6Emxi;T{@wOH z$Ubv|Ty~$l@}iyj!BPb%e$l?V_RW4tE#I_1jNxpE48v23;8uAxu2ifYm^O$aZWkNo zDM0KvMjbv4lH{>IlpDNy*CYZGfxbZTA)4V0h{ctH@=Cpzd+@`i}N6 zTX;1pmbjpd6RTp*BM5esh4f!8NW1`#6Ek(jljlC~-#_=K5=4uij+u$a-%NL#w84;A zFaZDh@s{RmC@GQv0o_aj0xA&3+yzAknS5$x5XNT4Gy1Qy>=>kDe=}#5(P;aED@XoQ z38bGW7%V(kxta58RrOb**rg1}u7TgIswSkgvVjWO^0VEWQnA6svJqZOx^qjl9Zu5b&7-Dkop1Xix z?g1Cn`MM9APQhlqc6#yvGL6rkKi_@0KYYmBRCcg4clO*lIjylu|3RpgRNPl+o&|*~ zp)K==9mwLo-L)VdX#@3Bu&jjxpCKuP2?#{2?N}1v$=AD@qSkt>g z2M;pC$MkDqwPhk()5)b4qp!h}_XR}UY;L;{`VkX*WyfEZ_$%R~H(!>ifmZGQA@Sm|6ABlkQ!J<~wm zeuSTO3OM#Ii5J?;$iRY>hkQMGh_&HVHHGpg>5z7m2C@FB>Xj8Yq+{nJ!j#pZ>}5C1 zBS|7E1y+dO5SAwU|E;4@`?Ys(Jgl?c_>BJy8 zrxS9gxZs7#p)){Vk!3=x^CGX932o{sXQ$o`Yei){EO#DP!0yU8;~BMk-w8 zD&1VsioS(|c2VfPT%jzQpVR+n?>nQS%(As{?3Ol9x1yqe-K`*qf=bS|0i}SFD53sf|u$aJHqRB}IN%zopnCBSkOy0Y;wc;H0q8@69)^iJlkIRCMSt zb+{SrJ7`I)*}pfHe-w&*$pfTPb_5CO?(+nc~XF;ThAL3Z7T`{trn-Q^oY zQ*%uZn$+D>F$L!+MisCpy8Ojcz$@y7>T@Y@ZTO-<{&eQqpMKqwh~9U>v6l|RTi+ZY z$IcDi*GTNCf#J?8Z`Z2TT_CeKNnx>+XFlv8>IbAYQSKuhdpSgMKuyzeJzapzt`TosvNKHS;0%mLAensAQ+1bTYf=kjdRRP>*S#X}%eKn)=z6 znoNn)C)?cbi4)V`)A!rsb|D$mgI4F(;`epB)Cnbvfc-*pa&O5Y@5L7GtK1?1$^Z$l zcbWz4$CF7pl#E^nF`jhgmhmRt6XEC@?mLxVXDNXp?CyDN^e5VYGJn{!lv=!Gmi@LC zt%ME%`x9^~0`qvyYVT1CRv7<$PjT;#cC=ZU0qMPHD;@a=c`J|sQ9>Z{Y8^uloFsYt z{gq)jQITLKXn$iXo$WnkzhU#{3*;k0(0sz3xi$vuJi9Y4&}kdCZL@*yuG?`-@jRxc zUsnvlS6cQ|2PL4-1uG`siz7}Sa<+)>=|yW-0@@3Dv22o1$3jw4%_U#D{P7xPLVn3e zVHFfa<7T05G@#b&u*xCGAk(^(`q5W}G!35P+Ik91_6l_A(7oKj?V?tGTx(Ak0J#8)Wy4uh$Qq&|Ie z%m#65l_J!h1XQ*pik76{GVOBz9xUym` zAFD>V)Q(@?wL0e`kV9UxfI=wKmFw5bsLXx3Vpawk(g-XC&%lg$a%+yLpG{=lFQT1~ zx5=c{c+98Q+T>HMnz5=kYe7el6}12&sqm*a)+?A$PyxL~gXt1bcG9rNGPoI64YZlu zB43STx1Zc8h@7L*gvR7maDNC2h)eYZ$cfKXZ$_)HC z137bGuL*-%4M+1ESt2%7?BTHC+i&0Jl#tbc6^H=!p?q)TezWU^qm|pxi;`sZ^1(Gw z7O3V8zC-t=lTSt~SZzE%on_8M28sICI7EWW!bTU}Yj;CKdp$?MA6tpSWbz!oriC-b zHT??pTTm(Ac9Co(`jYCvL~<^0yGqQ+rQ$}a>ftL*li3h^1TMYUQW+3)9RLHKG$?Ui zOe#q4Fi>V5bo%f%JJAaC5TR_;A6>w`b3E-J>5!Z{kR)h=c0&`q7AW;%^Rbb!jw+ji zgv)shN11g5%vP-4v}YeQ+ySzfg<{jK>KR&523*ox@0Ou(7!eRak`Dx-8VVj7G+<0Wfc`k!v$VJ5zl}UJP-tB}-AtvHKvA%=y{@kAbV z&xDGb4(s*8&{j*eC2~)rBVu}F3M`;fJ5&Q4OUkA(cBX1O6=f04TR3wgHO`)$zI(t< zT1N1PHgvAFK)F@@DKo0T8E`7}crR8)1`j83OKCDJ8LWkjX%4fFXigCMnS*qvOL(wm zSvCwxIQqCEz$O$1bqI^*s|yRGwrtqC)f&px9EQ{U`WoKzV8OXdPR3sjhQ*i@ zm(-3YO0*OZ#e`kB=PS*!!qJk!YPgl;L5CAX3yRt$wMFh88y>j99<5%1<*mj=uKU3l z;1rcvQ(#&G7>Jk$H15`+A-5K;>4708g_Y2Sl^SnU7Y_$vz5Kd`k(OLmm^$}%@UGeR zzKFH~#kM^XtwXMkY@TcUZ;V3jl}~{+vaJ8jquB055V={R|1t12SQ4EI)90?v!pKOp z9!US_VWEc>X4`xvgV69ZCam;Z(`%;^woFc^w>aM0eC^v|kP+A)q7EIZ1zmt0xd`St z1zE))oQecrU^w`HwqhAU$rO@k5vG1&&y6*_T@xMXd)9yyWk+9l`QRu~G&nH4cps8; zmNC==-*9qLP*+*y2@{t%Ma}dlYWy(g+!U_X$;kk5WEk4Z;kY2GOh6xa0yqfh z<2|mudHd40N?qfF_%O?-`VGE|yYYpnPYR9Tb)Yz{80`S3R(K+(Z&zF2go2wzxhNQ7 z9VL9ed{WFE9h-7y*%t_(c^kUJn~U6RLc~o?pk7mff4YG_%OuEx zc!%qizuYHX^=ggiT;^wkoiG=KyU48`$LaByL+iROQIEQm60DE3;sdL3orQ4J#Qq_?s`{udYgU0PnBD8FTOk^ARurj4rFKY zYrFbrylPt&Ju_o`Ck@ejt@iZ#T3z_i=Gkw@a_C0i0$YM6tal+_NW&F$j~uxQ4|quOZ0t^iuocPCcyJvkO!M4@*$a*K`4?u^ zaCVB%*9qqV)H|$ zzMu7Dn6of8$zW#=oEJ}RPJx)o4*x=b4XVDXMYc*6dbar9xP5x}7&1PIYR(Shhq1YNiYacwAEBtnHM19U0{e*7xc zK6Dpg7(@~xpsn(*N${{Sfy`OTzU@tNIylO~(x6I$3%mUGP>XFCzaW5FZHEzi6%7{+ z=D76HiSUD5sy?qBu#uM?$~x;y*o`fEToVC>aJe$&+X8{gol^mL%TI3INqckQNs7MR=f=O?n4R|UHgt{Lzts94Ai5eAl=jA*Cg z{MMoAnG$1x4+qMq!YF!P0HQ_;i_`#bhfDza0B@!N{;c}kFMg!LOP@awhS#TPj-qMTj83-78Hg8V6A>bw48YJ6!Y1U2&JZ*@+|0Li$ zp$$@OqHOCwtBWZ0#An<>jFSS+l(C5kJ<#1p)pUSzdWHggg@gMOsM`+`^ay150~4OB z4Qh?eW4C z3d!@&z9BdaVz`%Jiii%JgG$s#;Qtbl3Aq#oNg9#aH3>x~zMm<5a-zt49~x#)zzwu{ zlX)(Xvu3-p5yIGJ2$~!43cEL%KLHwXkJ6_(umF*zLhuLhbrA|4>Fn-CohJao6PW+uE`M7)jMgH3^XJh|pBiw;QemGCA`+%kY@$FUx_gg4f&Xv#!v|Km$wd#C7+%l7pIUmM$8Fsbuca88V)c4tyZkrav#Ol)@=n&hA|tfX>`Wk zUFqgIsWi31zDuKB6}=b@<1}4%%@rJgnTA>BYB7CJo2!~@EUlCz1r^#PMHTm?{r+#!M#1HJ0dcKL?E00-83PKW=aL*^7oZ&EO2L&i49jjadF1 z88d^3wia0>{-G3|cs~Z1Tk4>}Yr$Hh36tnNUqTRu1n}ciy@=`e6TAZqAxjSrTgY@!x|bIG-4|dKa<~(P>GQ8L-X}W-Zv=%Ul1wKlfP+k zcT~9#x4i{Hfq?hN{p8~kj_-cc=2{6OGSdAJzvBWFp(<`S6`S;C2FY$A;?8~MB`gJHW*Yy@U-`*PwGTk;H z7FAYX^|h^Ag`=y?lT+tj+-~GOl#4L4aICtZgs(x8DRAXYlJ*XQ1A%Jw906!ae0j{^ zNvY^Ob%3FGIpW**0)mDZ700V`sf3_N$wq})yq>SwvhOzxNK<0R zerkk&`+T%eb9ix2_kN?1i%RnvxvXpQRGILwC?2w~L0Hh(LsMv;2wJT0E9Db{8 zk{x$$`^uv{!z4Lvyq)3~UL6#4Oh3{61+xi#z4hTMda7#>oaDk<@o|S08=XCSAG24| z;KMOn{ZpBkNPd&3kWweTiieJMdXf&}F%o#9Lx(y|{XgVH;T%%ai{6ETC-+PO2x0i&+R5`Hm+6fOE-($ zE9)V9Hq^kt?4FVK>Djfx<~~BJH_slAH_#p)=Xo3ZX>2y>>)&|hmhI+6>N-6$&(Oaj zrK#ybDdvk$g0b5CQE*bRz_MU2BzsLrvUg`2btdiPTXrMplO8=d#YYRr5+19xXExWo z&~xf?87_L#W?5S56C50MN_YtAW>nAw=r8+i<V7-Y5&l z)XH14^xFwob@X_P1UcNh0N<5QQ8{t+QQeBKz7qNV`wxCiYf*o+>N=MD zSF+se#RU*KygR!hVp+PS&kGdq9KcaB&t?`= zoPNgW&woPHmx5?6LXnY0(hk6IqXDExR#l1W!HG^}!@p~e}5*;1R z)72}*)KiOhu78nE%9MmpAN_9l=6*6o79pdzFxzB3kd|~s_5lo(YIM75t1|^UiZ(`~E@R?}Q#uAG^lN0g zEhzf2>R6q%A%)$%h7P=rPAz`VX)!*d2M^9g$7D&C8=K5m?tWhwI<{3-`?-YaZB9`n zXTJ8K(cqn(v%_w*2+8BUANMm}IqV%dC&BrauQx1aa&W8QiAzSkwc3nVHX2bqoe087Z5>yiyRe zk+2bUI}d<2YMvad(4T~KT&(b;c#1HGO+TK-b5vCoa0vquzjm?v5TEZ`r2YxMa_#!{ z0RUSd+V^)h>WGqa2}aog#+4J10&{Bva9F8}4fi!H;6P_!z5@+s3#PLPc~lOt99VhT zC-X?EH4(5%5$++u)ASNFcu7>Zn%cCdfgthCzH$aL5w9=VBGSYcy;hoPiv_^Kuk3Sb zakg_YYQWLrOf}(FzPL+D)BDU$f zs0*`+Op^&HB{Xo3Tqfed4jCv>2Y(FWPg3=fLt}X7E;ZCc27xZ&H684W&$CF)4XKI1 z-cTQ|Z?kV8+k;dciDPM+nj!mDk(+o$P&%`KoykbSrx;ClS$qK8?Pe5mn}2DewwUEy zn)hmVdu=>ZPc3vONuXv9S=!*S%)RCu`0qNdt~ooc5Ct)|s{h8PaKC5Tfz|RTLrm(3 zh)Y%VY++%Rkw^B<=af!zQOUiilpb+aqcuGyWnyq8EEsDSj@)swj(vT2q$bw-ivQxs?Dg-eCY=kHYDv@S*bP87Am6x| z$ldc092a!Jqgp_CqdGDW=wL+C#|aZHt=q05KHi>`XElq(8kI)@Lh?tc*GAnFMaU?f zMmD`{b^||u8h~-lX!+)T>E&htJB}ny#pAd1aWDodR28B9X>zzd73csNE7S}C8hv7S z?4}WtUBn3vbX>%G+Tf=d1lA!D5h-8=(oZ)s3<}!JeRK87>8SwMLduyF92~6ouB61jZ#f|U1vUuwuRd8`S&MhLMmkSMv`{K3+?R`9yr7Tt{#%Wk|WtxhO(2NB_QdrL2{4}40@I7|JLAlMwW428lg)eq5 z3IvaRld?y!l0&tS3GG}4`9H3lg2vWIv;okC?}D{>K1Sj;13F;Oe_@Ub|MtBF@8P6& z`}#*^QHKUOh&Fo*G*pc3hjLnnbJ3;b_tmyCze z0>Q7Jju@W|UN%dfO;+?yUz@k5XA;1-83fh`uC)SMumLWOYDh{Sk^nkLfNU?Wgb#I= zn*u3&J~g(UU%qF;3sCT?^`r;H+gtq(plE(J!sBQ&UmW7yXYw8*E3SuWvsJgm+E+L- z!V%!e)_H>&*me*dXRdZa6tr;h%802Poj3EttrV z;CRmw)~c*us8 z?X)YTe?CjcvQ${mNO9Y+_OE_==?^|$Sab>=|EARS^HJ*;<&hdJr}*@quflhfv&L2U zdp>{i9=qgqUB`e5P2?<{8DU>UBI-Iov59U6rJ5c&|44un1lJ1KF3DtIkvRSR$zTq# z!K^TXP{Gs9v1^q^VW0_;Lb_$lQp_O&`OWsg8IVE0ORBfb77-Rc4#1@md7lX!0tK

X1!S0xt7`SP7iEYD*3h&-c zLjc(hdPg~lT4Dz&=p=}i_YS;tn^=V`SBI#|&8PPT+* zaClE^ya>2uT4-7Z}x7y6Q7q$}qak;4uZFT!XvJFq{I^Y(sj$&J3b*`TVWP}6f=7Ix=T^gXbsWz>b@!Cmup89fmZN3ff>zTf7M^DcPuEnkXtO@bCfy{ z6iWk^vKXs^F(o1>u6;K8<{)|h<-u>Jgg23t*vSl&G(eG}C`g3oOj1ovFs5DhTglrB zqElzGe8^8+w4x^kd&;X-Sxa}xWw7GEYro;5{u-zK6EW0}cn*a5;K>z&zk2wE}wkYZ!^hGTNu5`&b z9xkp`Yt)YXq_?!b|FDhf@5QPg1r0c RAvj!WN9jk>51sw_KLBaa5zYVr delta 112924 zcmafb2RN4f`}b`{DSJf-kv$WcWrdWHl|4$?dyh*bDL90z{r%v=KMKj&Tx{ur1I>DKVz%G>ZEi=>9iFBJ~?0IbI@uSLoJ%sgkKf)+UNE1 z_MVuXeYvyLpQ@TFx4yH}9LaQ7U7{{!QoVVxFW30#)4;+)-rnqoxW#5a8q)8@d|y7s zwGgc{SY4RZh0|Q0rvAj>8)))IY zu+N=iWMIIcCwaN5Axe*w!c2! z@#pnEbP{vl#BOVA3yO%iYh+}UY@e(4v_D@z+GAM~IXiZ|#>_lXPZ@q z(r~4~?&=7FUtJ^ReRAmB5if|pyStmEntIM=v^p4{lG|v1eIm=CMkL>$Cd8sIXX1M_ zhn=HiyHIC&mdHy2+6!1%Sl6yyyL$aPZmDIzoTB1|)sd<|7=Zf0LF(VXe+SeG$>gl> z?cIgdR%SDD!Q0!rb;d!~WNmG&U}at1#LDU%78VmSE&rJza{4x7+qtX0@@a&T^7My? zFNr9kdZ!ZTJS+9WySh}=b2TrydD?3*Mj9>s&TIYo^IMT=%j>8p|JSdHq670{9*29c z_NKq#7ZBi3<$2_;wCeBgPmWJLxx#g<3hRsVa-n>^O26@OqwV=_>GUlQ z_6Hgz)Td2xvuKAZI7# zU7ee**$!T+D+Rjc1oz`_e1*+|f$~1w{yNi+#E_4@-(+7|Yh4*C=X=(5wRt-uoK}>b zVwY>>C|*m|I-8p3f}3(^u28|&T<6sKM5A`O4ejFZyxf7aG30Ebkx;VfE`3d0Rs5Sb zDf_3msK%FzlY7yabLG91Yy zE(GI&{<({nP<3DH<8tfU+HboM7`>jeQRo{Be;d&^oJaJ${QTVZ!ms4a%s^^Ei)r1D z=D$BZ3JMSZ)|D(hv9NF+L6Bw@r|O!JLmR0}D5>Plm`BfVqX-EJIkgKvb_pNuECoI) zHk*KF%PT7SRaRE2iMnU2WoBkxFSGjN_Y28Xp8D#KJKvj4%B}z9#Z*hg)WWY!J3Bj- z#pj7awseQPE1|HO$D4vl@dyZBCMD5RaOtQUotP9+C3$V0Z)j*3Us?)<$z)<@zwj{g zfyP^Rq4W7VrR@Ct{8ytD8OSc!AMe{=5p}uf;o(tOQi46YyE{ja;JN3bwRZa9=os0Y zX(unJQD>J;5V2j&9{BV~K(EGwJVC^{0qWeu)KnDDZptFjg#bGj7dbC4vCqZD6U)o5 z-o7OphOMNWDE2%w^j>notw<5G&cv_}AFjyqFRZNO2?Tp{C#zt?KX2E~F0Zld!j!08 z+pRo4K7Re?jbCgmm3p=s&eyMBkppgvcS2@nW^TL7af2=C85!>uZz4~O2MUd;B)lq2 zbV>0uavbcbQBZw6Bxa$=@Hx$^G(XLkza~}XvO*?aw&tIkn>#r-?B3a|Kfw0mfU?iP zK7qJdb!I!2xBV#Qc}sM$1mWA)>LET=e;~6qHB%`7VZPw++l1&pqzV z#XNRrmxUf2m__YsH@?OGf(Y0}srO>I2fjFS(20Ab^;#xHMn11QJ;V>A7HsH<6CjVc z|NbUtODCA}XAeDPxX-tvZvb@;DL<;I>K5?Db!!LrHF@HV|Q zvls;hHL`{B#bBqfZVG(9rd}zW7c$)3bYal2moHxZ4Npk=t+SLtj(mX>;hZam!v~7# zYUwL)YDaUbHF=Pw>v4e-1S<{-(8#(Fm-SxVT$yq71KcacQhfOK|0FTN1~`#pSBx zc(GH)Fgr*(7JtU~Io%(+vj3CG=+{@FGtBypv(19Krf3p5L7T$#m4Vg(k zAqk?c?9nu0ZoH?*TgrMBc6a!kYIt*c&CSh&-*M?q?r%(?E6ciz{K!&&f&I*P^7MmU zXaLXxn}Ud$3O)A_8>_*R^Dv8k?{&ZKIp5&tZ-7>uqm8}(J5N^_K#xU#e%|&(o$H!` znAUU~9cGmaC5GE!jEZArRW!5w{!xhO+$Ir2F$5&SogYlTi$;H+L*Q5_0Ud0PqHvIW> zx!h(%%EH2;s766%kq8faeypxelhpGquR@!sq5^H@UQvwvHCdKjD0cx+xa8%79$edH zZT@hyz3r54exF=+l&;B@=Ygl2TYY1rbkTl{$nfy6-DJ~wSt+(i`Sni8Ggfb#xY*dM ze0-`%5xV(DPxZe2EOJ)uAj+hfuNT~%dbjbrVhwrnJTToq?-vA~{%p$EuNt44N-c3wHg=xV7+wovw~n3`*OUaUK)GLkwx>9 zcCz->ftD6rIy$<6YIgw>6O;9?xb&=Y2v@Do2{k~sCU}27({C3ORx7WKJT*Jaa9rt5 z^|a7Gj%uHA+jrrUxL;Bb`dzlOCT!-tXYX|}>?U`E_Wnn<9`hLEt5ZS|dQVNB_s97b zb)T(;)}76c<*Xg7;&gX+uWxNN0%97Uo_=9ldmv|?%t232U+r~3LQG6NP~(XRox4Ph z4VThKIeB<^WaQ*bnnTC|+XkekGiN+V2mzGEQ984-atYuL6%9?Q=#@qGjm7oR5z$RuYlOP>REhlz#zT&1hq?8K=zF);0pZAS*?C za7c(>Rh4J0u4}0|^6Ok=WMpechYVn5GOajkYwPYzW%?_(9Iy`$51$Q{<@Lx$!hD#v zgwx{T3DY>u->v#Q^vZV3J;SMh^uWwaVRZrhMln@ixr_XxQ6p{;!Rh_4Q z=9ZV11^|@F&SqO)Sur*;`dCqgQk7MUr*m`Gm~KMo$gO^QoBtUanwsxUeKuNstGc?{ zx12IF@Dj~S*lZ>MBZERi8=+0|dF|Vk%AHqwfAbQ6jK=AdFMsR01yStQRnJjN-4(4`jp30b+v81G=^y-9tDQStIm!urC4EmuiP^5DUPSP^G-wTBP!mtU*Qcyq0~%0!nbZYJh<5a93$ z2?eUKs=vAkl%`|vgeN94l6ZS(hhun*qUXMgOW(%$1Z~>Ww27Z1y3M0=5?T!_G_9?y zBeE*bZ~Fq4%~kpQLGk{*cQLbw{nRY8yLs4&4!7$va|}`>$6e?2@PK%8>iwJ?4*MGY zR-Mq1mi5C?NuK2&r=1k!W2>+t?$K6>B#L`xl!!adDpjVYPI-pi(Vd){nt@s!^VG~N zr^HZ$KTe4|Ql4xGI$4x*l7xO@RMf-8*kJb0dnbF!`fw#0Io@bNHlYrDrx$ZmF^YqF z*NRJUg>?<~`!Rs&bc+I5ZJSwHtkLavWo6q$U03zy%h+93huPMKD~rXT1jF4?J`}dex`!xfp;K@Fj2VPUN>1s1G+2(BycPw-qvJsYd%q*jUjy017 zbQH{oPmK(yjGkWl^d6V0S|HKLxaocK7G?`uK{&EXy*?xI(V6M$da=-v!?pT-+4`t! zMhogj&pK0v#9ddf=#)HD%^A=r1^UgYS3&eJL+N={mFVQ;WTDj`{)<$+w--j|qwh;e zVFG4dUnw7>sC2n)%!n{DUM0KGou?}VC}?@4iqfG{&%8*2@%x(h>B&Q_QM#jQ{MgAA z^#p||7V}RnwE%di1TEfv&z%9TK`eQ^jwKgHjZ>{JJu}f57!(|=TKNMFmxpZYnxTOi z+u9cN@WF8YZI|60dTPl_})VS|^y&SD}zP!7eGcuFOJ7c2lQx{%e9*NXP zQP!6k`l@=Lo>k)1;?Vy}VX(7{-mLwBJ9cgNo!RXqf+sY^UqE_@OiT=ib!*y}qfuB^ z_6??=7diE^4$Sy7ty_XNw*bOlL2uEtjiII5Gm&j{7Ir}Z_%6eytP&Sc(a{{D70Dr!2>xN z8SKFlbF6Ij5V)=s%ZoulK>^oamY0_gTCx&FlZ%Us3-?>T*rFUAe!|GabO~16JM;IK z7KPYVO$-y?hE9(*ki~BE6+VV?ZF@VD;x<>G2p<^<9jfZoZEFiDN%nPLSZhdCg>sLF zhj;t-ZQn9!Hm$|pZ2z^|qx`0pA<$=lR#Jxr2bC;+3!{PQSAVW>lzKM;tt>o#`jqL~ zHJPRIs-vk0NkuO!8^8RYDIQ9z!Sb{wMcUmDBg)HPcGM94NtVX-+HAY7^C~>++3YOE zd{?dL>Tpc}aH@kKPhjNNT_nJS@q6& zLf>XpUp)f13Ji%seWm!IEH&1}GPm@a6a71Ml0FxK38L@j#trpMicSvse{Q6*&3iTP zc5qNHF%?yiWq&>fN>5+El$JoX2=K2(zXKxIm$NJXz$5%^ua>-md+_q|Cz5`1BO^Tg zPKsiO8fB+*KI}&n^CO%5saVBr)PN^dKMV(DQD}V4OBsSxkhqcCdFZgpNKd* z8~8x!DvfUJ=W4Fe0DQ_q`zg8RT|i>Ur`SM43h`a&@3Jy7GRaKDo9t~@j;fnX_>}J7 z-ZK!_snMUeM|lrJ~s~u zX6g~9z)LK%Z;PeOM0l?wBQfyt1@rNUh!-key}D=}sFF5VYB@F899rSFk%E{c<>gt& z{VZDR%cb<#S=3;0rj)xzVhoiUkC3n>PT;A!$TwggfS7ytTk)VE^mxy3SK^``P6=(z zdb+y(@=RJMa$Co)I3aqCm1Tp~YwreQqa|8DnlWw7eEqsW<@FRr=3BQa^?0aBfq1PD zjlP%gKKuv`ko_&ECnA-MN1{L=!{}`tC?zA4CUEV|6}*5MraW$XRkD;Ea^9!vfBJYx zg0Du)ug`ZgjsA)XVh?~ud7CB5UW8=E^qJ9%Ps>UydoUyxt>3@DNKTF{YeaqbbTIs_ zq@-kIVfBhL7~6GiR5qGj!^p%0)7#sph#@6}2~?}gmoLjJB8q&_%TQ;mYIl*o8%G&0Z`yuRDI^jys#3OO#Z85f)Zc*2uuf$i9dE zKNVKL%D8l-Od5mT)4}%1lYo+v8+h25t=C9msi^9k>TeGj6B9>Ys=VUCNc=GqmxF`D z`mXehipJ2Q+u<@XFiMnwz&-d#`&bEL;%-qGx5rPOFbb|PzmOufYVXF`fE&j0O-*H5 z9jhaZOQgHFv%?TAe|5@EgE8m!_6sRIS;oirt|7jObXTriDWN5aHr24sWfI(BHmLOo z|MW>oKK;rMsD5wnv66l5>cXR#HHm#66G3;R7cEiQGK4L8>(=GYB+0vSa_0hPNL2$E zNrKViOG87mI}oJ^-@1OdI;5=eDy$em-b$qX`}Yt-k6;+mpdk6{3+AFI_ys%=Mt0i2 zvj-C>BNE;w4zL^^k>REe%IHh5B&K4AN&mUJApGB+THe6K$e5zI1b7BrkJZ`xTv(YI z$u2NZgicIPr>&^K2eroz*QGDfucp!YT!ts-Y86Ov$29)@N$~F7J6=A%ZWnpPqOG;H z9)7Vt*^J-x?OQ`%pT-aa+yDh%WdZ7cypolyp}ARhDEVz{aPWEb;sQVq8Ub1^0>z=n zMH}vwIojvvNnfrO14(dKg}v#((2zh;0k%UOo}kSz6=)X~9y>W@(r_z#^Kjp<+h-7w zgn_x4$w|yOrI!G)%tgsyhrNNRlLWSj>iT#G3-*Oh1CO+fOti9t13z@GQref_9_i{b zv9T!(850plzEfYEK5Y)AB;RaI=xzM_%ur~9>FMcZja?N0#O+qD!a#jeyS_&fj182u z+l4Ca$uYl-jQW8((q!&NzVQLZ$~jsDwl&aH>%iMO-_UP^9{eSC@Bf^LrGJ|U=BpH; zMUUIwNR`9Pj$Z#eJ82|E+80GdMM1hrsh>MJIT_RSYnO?KPq-ZA2{W$t=)nU5m*s&- zTLjh);<7fXpCZEHm~sk)LMqAgaC^ZsEFdVzi6-iZ1bVQn#OU3l=$(5+#Kh)?PEJm> z-Y`tE78W_0lWZJn*<_&9-f4fw1xP32ruCrF(e4VhwdwBiAMGzrY|{>vCP{kSEj{V? z_yDMfl|hLmN53zyl)y<0ls_&Z^0Qo7gIrv-V1DkdjQXF7FJC61Itsh4e&{*<@m?qu z>hi&N%IK?ORj$hRs$O77z&*L`EJjZboE+_?0^`Zj%%cYE_Z;j9_P&IKgiD|(3kwU^ zH#g&eTK#Lj@*ED1@#*ow{7^X$*dCxBUW8|S5OG#CwdT}C$}T?bOlT851?Pw(S4|xZF@e}V6hnP{kRWof8#%l3r#Q2fE((01w{G&R#KhSdaJ%ri&9gt= zlOF-)u?JV?6%`+&;Xw+?V_jX{X4wt2dIWTpS5OcKbw%8RELASd4`c+G`>ynRlpV2r zSWv*#duyNv4j@ocIXN%F7i_F{-`do7VhyvO`Zm!T$rOUU*UnRijXDNcM{SATw8qB9 z74}o-C^)r!0m@Fm9iSO84EeXgHV*KX9A`W3fRa<_bK=qZnEw+f@P?KDWnvW;7S;ns zo>*GK7ZMVJ-_pUZ^+O5@c*3vTN^!Z!z`#HhECWsNE{q@%5s@#5w9Q)pre&+4Rud_h8tL<<4%0~ZO;Wo0k`1RPX72vb0 zgT(Q=v~0l%xMZnCj-{VJH$Z_S0suzLBxkff{uSlh(V-Mp3evt00zTL3XiX^0$kiJ+ zN|Z=FQQCDr;?zPmuLLcBqq{U{es^GcwxX8+A=I88T7nyZD<>zXU1~uNSgy$I2le*K z5G7zcS^smFh^{=GRqI5duA&rV?xmr&Jh!n+k(y~fA3!7XpJX&cUceJ^xE#e z%X{NShgMnI`OS3XUd;H!1jh5*9|tI5fM9`elR}$DeV|+Bd4JWh1=j*v4k2?>i{G6aIn_vfFHdxlU49g zu(qD{=hK3n1Z#~;r$lAY=2yn0d_=!0V_+9FDo}35XJ!IGe53{qS{l}7uT5Z3)@LGf zQQwfo5so3LWTMg0(F%u|3+VX=_>bwy;Q&R4ATcp9gzyh&$x~pm?1R*ZwhB##e9FtE zuM(kW5M*4fZ5J}i_aY9S_fa3g;<9sfZ2@uTul_iH5d9LwRWLS+oR>7`mzJctV>n1W z0nLY%+>w!yg{4C$CMHHiOM9tE`xW?mtjr^8AXHMMq^6?12vClJThW8=$y0WzQZK)x zRqxLB_V=;~lewP>RD34b?|2LeQ1bHftiN_&g@v`3oVvC>=5J;V)~h`4sfs|2?!k$1 z-J^6?x|616UK=}_=Lrfqx>~}%^aI>^RaltH$Hzw-_;ryz39*J8?7P-~jF2ubtul>A zc55RB;V)ib>s7lEtC&)agY(S$xb?MH4Q+f7SHb61LAF*97bC$ir1#m!K#i}g{AfxH znir?J?yem)x(- z`!ZWw^okzy%8Xli*kt}k%f%eeM|<&PX8*%X$c&~63Sut?`Pp=JxH2d#toiek_6uB@ zihfqm9%0F(K;2y5+{7FW@1+)TV*M6Dk3`gZA5DVqf-dOz)t3w(1&4>DZ8o%gdHwoz zudN?Kihl<+(H>SF26+2l0yN(ImYLIC(-kO8M z4;}tFtjScxXm%(l-p|KGVVD9y#rQfjL<1_1n68`|2=gPaY0ogG}$ zG$-QcK|ycPbs$IY&(G-c|5gZGHKxTS@Bd2`|F*=;$k+$6tSMAA5G+DX=)CW!E4=Q{ z(+#{VV1{;Wf#fhk3Zkp{sj`f$tntC-%mA!ZkV1*c$X*!KARaY=#h$6q?Lh1=?5PF4 zjKk-FqN17y_nM3Q?9`Nh438n~bj&K(wQu0&gVyaI&7no%{vjrY5?ukj4jh(8YiQ6G zxQxt;$&`zu`Ls8T8?ezD5R^VN3<0w|z91j8-|>y9mRK=&eojtKA~LeHeSQ9$$W1vV zrHg2#g6(0(?Q0~-An<^}Wq-Mv8n6mh)DeHm_h@Uf9$nQzW8dZ-muH(fp7s|+BmpJf zcaQ4-`0=CHo=K-}Mg{g|l!(*(Yulikqu~Us%ThLXf=jrW`v+Nw@OT}j<)Q0J7x7%Z ziUO-KWTnBN6J$rUwu=P(UIEtv3zvX(rTr)oE!mhqcdc*h0~0S@m!5=JwYx&c=W<)k zXYYvyoc+VYNhrMs`Bqcbwz4`p)N&DYVX#e5ww||Oka?ecCwuYor3oy)uKs?l%9Y{K z@PPqs^s4}IDb~>DG*Q5)qw&E+LjbygoFaR5XW8V^s(`*^WCqdVSw&UV6k5IlSGdTe z`2sjY2Q}fZfJgiwNy}iSlssRJ7aY7dp{Fb~CkY#od(2up%VBa2lUQ}ees%a3ygX(; z#o7HGY(rr z;lDCfczTY1<0in`tR7$pzW<=Hj!9naofAO$-Hc-RXG&M1=eU+i)XB_}Kug0`pC9zjwAy$2YG%KT-P>wp9 zP?$V^VUU@HVkjm+$DDD^G$|KcbQy1^EbqV3~&8;b^@F z55mi&0p{HLxX;BHiTCp5OWvC|UWi@CeT0bt z$f5+Hr2t^9kp9yG2NMH4b0cHp*=g42*O`{?NNauhN+mEp2n`cjjB|^^OlyLKOZni zpkVD`YZ|k#fZ^d`=fpX}^%fXGxdyVB=ou-Goh{>cH-au11c*U`z!G14K>Hd7uzesi z&=5+=10#?e)wp>d#>mvPkq&4?&Tv}|nudjiMKVXuZ~KX)(Q)5~B~%H*Ns~#@U*gGL z`rkRbv&S7;H9|@>AqD;wk^yuG?j8xjaehn^yvma$y%^b^e4XSDH<6t%(_g;1P@-7( zF%cN_G$zJl4-b*5j|AD*T`?FdRa$XiaNv<8mDMZ0S9yh&HWJ)BaI}}E|MO1ZX2>r^ z9hHLu89V5=M|H6Z|#rOc8|Kfc)x1}gHTu7MF>%2e2z*~78 z?san`{RO;0G5o(?d*v^c`sbz7iRib3(QM&EKOOz(u?GS0pQ}#)`%^b0(2_~oHH2C4 zG82nc=i!(116Nz5Flq)PGYHUDrc-4l#A3=uJ9D;A)e*4CPR_0V#dtK!J zJ{`K8%$u;@-p7w?AKdNB5iFs7g9(}deCPF>MKRygAa>!GoJ>p#UpGbUg%rcZOJT<5 z<~Zm(l|Ei+s@CFLc|*B?APZf0!$(^|9K| zA{ZmJC}_*-Iufg#NaO!HeB`SVdyvjRp%CPZKN|r94-K>bRVGZ@c^-Yn2GS|5rZy4E znWZMD3rp_%oX)$!a@z=y%uMQiF?7QGp&lf)1b4oUk3Scul(C|sD5IQ(i|;5fpivB3 z1<>!8?0Sd;0x*d`?+i?BlrJ<vYFpaTL|%%MF9yE;uj#evx?Fcc^^LT=W`9 zRZL2&eTB%G58y)6?i5*Y8tVaGY()pZdLIgwE7E#rvCSD3IL?wS~mlN?E3uV9s&|cD&#Z6NQUOxwo( zhQMF(@riF+4K;)vfJ2IW)G4)??@Gq{n|<(t77gg93$zCyU5%}+cr-VyRH~E=&{nU& zQvztbzK|A@W{E<(u?OG5^GXGz6$CzSKmd-PpC8((K$9diop*6T!z%zB>B216t|8b^ zmEkg)8^A{pk&>pWuYt;MY-;L{4pZi85pK=?XoQH#g;&ngt&wO@7edZ~CUYQ77(*5- zWDYr65xPh(afO?kdmpU137`?ck&IUcOJx)98RYan)Z>JG8la${5EzhN38IiN{yoRE z^bB*+i)j4~%dfZOvAa3DyizscoQF+(sQe{Z`UkUM*SBDy&{4CJI?X(tbe+fGym>ww zEMWsg9}e_^k)NLgnqY<91m=eiA5bVDxc)G+^vH3ta6a^2w7zUoB&P9wx-a!E;bSv1 zbRHwb8=b8J_)gveLdHepiZE<-;t+ngJKzYnVOL?GQx?8~fw;BDoA>sQj-)v$(4)Bz z=^*kJr{w`+Xkx-1+gT~25ZhxA5uru__`^cOhTMYM2Jrefn-}NjgCK9iMsZU~Ufwr3 znE`wq4AeXjSa7G%xgro+QT4!@?T~}bCm$rdzjQgaRTU<&?~qaA6BD1*K_PA&P24JS z)QSYh2u;3ePzV>WW^Cw$0@UA{cc%pO@L3O*d@Zvcdb?ZsjSZgxL?N``U9u(^8fP0YW(nyM5uq; z`T1uTX+_9@ccSB+d#fX%uVh)G*gP5>A!I4xY5ISoZ^WqESBW5!f`w1PsZ#mVFH23? z9w|akKhz|$4~_+XO-(W%ZaJPqheS#(V3(N9oqE zs5HuL^goEXt1X;pTp7v!u$GN#_4d~TNu&3{_q9p!+O3S#K6 zW?lEVjRaO6$8Kn|3B)bYk);=)C;_pGI8Vw_4}}ZTYu|Y4j?wUjtuI|Mt{MVPXmts4 zaH)XwkbQ{ql3U`Fr334ORm}?SelVyrz*T0JqU)TOC|K^tV?%c2im)9D^2RvazcVlV z56+~~5$x)Cbart4^z;;{7}d9M+E4)Zj6lYq0JlJ56oxU`LASXtRS{hWpzl<`LWDSP zZu7fhgjeY9n2-19J*DPKrv?5LA=|HgxkV^nbP4L6kF;~P8JDlWYtHG4xhkB#piZh(5k1q1e<=K$)yxRW^Va5#GFe=9}>&eiC_Z zIJGf#eUPA_V`1Ck0H-a%(l9=HqdgUtT;sQIaz(T-m!OW0Luq8r7g)Qn4dd%>Zc*xW zy+s5_5IAUOQdV3)_l`f@+A6eOidavQAQv!uyYjR&$~1wb9B~xqHellTJ{OjJgU>u9 zXU^sH7^frd&=AH~7 zrY0$4WL6_(m%-JLeU~6`U82$Jk@B**{uCu12+Wm95~~*-jc5{c+tp z+v==?us~n7mhK6aGnWjJi(X10FARnwYpn!(RP%=~>@@Gd8 zqFc;+xP+&#dVAILn$;=N)YurAgcv=xD8xM8;^E%Kg+wB;|2gwj%ws>C=XT~@s8jG| zfB%eZpTRS@><3WG*n3|jtaK+IYJkb++u-$0h!fB0ja4cQ_K;aOYmFLGGHU2oQ3U$N z6-V!K>1nSgWGLN7?r%)(a#@T#x)+7^d)uzlPxZBHxgAeue@{I(IYp|H7f&m3Fg?W| zvEe=S!ISo5K|zGCFA7~HAwcyFRM8E{k~H-8mZ{+PDP< z>8#-=2VFe#-6J$Ri(fvP267LO)M#X{E-zat?IoR$ITzNQ;!a~VK$PC?crUb+U?F9Q2VI z>Rh)UjZ*#fZAAn&Bh@%$#b&qd*ZLwG?@sg;3oVLPr8v&n3{ z9wZ086+i8?+glaie?^B6H#G6Pf{jHv{^i@ZZ@sMRbnlJ|5=cpFnl6$dH?qd?@^pNB z?@P*1x>?z8Q0`Q{E!TIRI_C@ODe^V-zGttiOEY?VvJ0ydD;pOOqLzos=|{wB#`Ovq zzPNiBkJX;Y*0KcDJQzdh!!Q5BLSL6yf0nd%lKg$*_yFI|-rUzeaF_gGU|;%L&#zzP zu9tqXJQNT}%eCLjD}nVCF3fuGFUOHj6{~U%2n)O9b8H`IVkp&sSbwfmCDLyBMc=);j1hhj_Tb*-L>pXYKoNY%w zUxKq1co%d+M4TC>EpT*9lf4nlkT{-S3aQXL7RG{~7ZCjKmLj2Er74vXZ(OQ7*{#wkAVz)Ct)i2P#7j>8y1xF*pQm=8 z1aN^y6Wc+PNZB! z=){P+yLC5aVhqs-pF~}jU!d6$5`Iquift{jbf zP76JkQC-=Ncbhv=g=RlQZdlFxbrl)`taNkVdx4Hc8`c7P8_5uy<%z{bJ^NkFCb2&6 z3%xP*QE>+KEiH~*_6jF9oKS?4>+6cBtKG~dIqJ5V<~zB?PL0wWu6MXx&DEWCC=@55 zj*gk*%8wsE@V$S8!l4rTSMqt^>U_)2e?Yu4bJ=c%!TW0(9saVjjvBBd>b3l&Wm)KU(b0H*W?U!pQ z?N!c?hZ>(5Asvl5x(XI^w%%y})0IJI^cr8&Ja>ncEX6azCMc}tCHO;Me--EI6hx(_ zVgV&cd3H8fDl8*=>PslOY`9zmDqWvN=nZ93{FFuZhXN0)f3~VrdU@!GDE&|r!u(Qg zf;N^B8S1^Z*AYcFBOX`8_5~n}>l>R;wLTGeV*Kn`Y=M;m>2$7^2^@?;HH$f+_KzKB zDpR<*FOf<1XKk2$ExssNi%s$Ll+w`TXg7iCxcjQpYA-Q(2Di_Aumm;Fs&X_-WuFbO z!@K~$jaHy}GSjgj^oxwT@+LH`^1#9e$jG4Lkg2yf(P#FHLbk&)OE>ez7yf7zRV3xy z*b)8sKz#8VEv2jYn8>9c@j?%WGexyV`>*dV3PQM_2pk!=BM%R^l5eXci+dG9);Y(c zC02hh`S_~TMauZEzf1Fm9t-Z1R;3pR+Z{@O9MPGEv^T(FKWZ$!1I~+!fIPImz3|YB zKW)4@R5AW)`;0@Y>Nc%+yGL>+0D9xr*AW{(QVK9o!1LMu!$JAXNQBpw<9x3pj`rhk zdkhQ#@H7I(kRvS6VjJT7md2Mv&l#S}sm&_P@WJiPzB^L!g#S@3KCKk_AjGK^Da%Jk z;FthTn`kwnqbWZkGZPk98K*;ln#*Lb@28>>C3HK z-nRXUB_dxpD1Fv_1-2)bZmCS}?>zVd9BD{8<;bsh03X2kpw-CHHu>JGF!0%#^ku|8 z8KgUTrslU=3c}ws=~uaMLDSRB9sy!WIrPqwNei&QVr1;SYb4;MpUV%UX8#@=8w(Qq z^^leKLN*lI{m5B@)}VyvEGPAiEJLe{>bGW26Fm0_=mvEJ$Cj;A^1qL08z40v=}t%`n1&C>D1P%T4;27QU#!(WatIvON-NQyH zeGtYl_44AgUUYKv^qh#0jF@P#9wH{*@QzE;&0i04j^z_a6=TlUf9=M=r|4+h?_{Yh z-)&Fz8|A1ACgpYM5LQCsg~>rP_zJD4$U=iv;_@b+PN|KCkvN=E5HQ28@BWHGdMqKW zV?iB$MIz^IMQy=;pX!?m2sFM#7d;Gtr>Rdn>4aU5d4@qj87MnG{${!yYacS@b9z7t zu|X^v#zyid68j%7xvUNPgTC4TVJ@0fAJ%nDb*TpH4&(zLK3I2+Jat#(^oE^ZyvSES zI?i^8KL-eU=amfFOqPULw_SNJL{2a(Y3zGrxkI9(q#-=i@8mBA`3+W$-OVr9*`Is2#Bq^cYr*_BF!5^ZXTKgy%}Yie}f zcSN({Zco>(-%q$hcEXqJH=d2FX$={_4G!_L`Lf)+MOj!@q-lk0G-EE0mQoh=b4R-m z0eTyk^tK47TbF4b`uv23Tfh26dV4L`=V73L8w-Vz+Ev_WBYusmwqbj1HbzE$CdhLYCWnG{CX)XSUButQ@q-t7ERDsPJn+`v#JmsD zl}#!cEFL0a)yqbm=pdUa-AlQ1yn_64W)E)CU6%-mtGo;bl9LTVtqkKGGqam&57iJj zp=fOJ7&}fWXQdDfg^TmQufw>*3V;kqc>m?NE=oLx=VS^zZz2;CAcb5`VhU*jEG%!T z%hXcX;*Xy^ZEWa~LPrJRL;ohV-UbE)+%l;9>8FPZy80q6lF#VzC$MZ}Szcp#U}|f} z{*9QiV}1DBi_{U6s3?v6wAyrQI#JLIqWSY6-Vb7XVB*P0J#J5X-8PeD@icYiHQd>& z!a20jkVWL;QU)Tt?Dt%Cko1e%@Bj__HQpfEC~FxqPa zD?va0J=TYyFaM8R-~TTHIQsWC*gy0a6t7RZlkoE!Kw!aA^a`U#1lUwcO0PgV#bGi{ zCF$EZ{)1ob6^EIvp0TknFYX~FuC1e!Vxdt40qecLj(}{zBhjgJ6qka>8`T6#I8$&IB4oy zh^_psja5%JlJoX+yO4oZ&;M)Uq+DQNIuS%4HXVOf08z%Q4|aB+!aF)Rt9=qQtp3b( zz8R%!tYeso^fz^eg9Boohf)ws)cwmbvZUZFDGnzW7h3F(7NDS*b=%!`FPbr^^=gKf zd_XUcS|tbp&QL^oqWz~FB7T8lBQu+f6<+utJ^>+FGqVlxJ+c|A6XyS4v#*{#Qh6e? zQ9SDZFeC}*kMcdeOc0X?OUK}l=W5p4a>xVPDwDQ1PvjN4PmTmcZ~gx2I)*bhbT;Dp z)z0Blsyg?c>aT3>L7y%ROU(ieozm*UFF{#W>t}k+-~L)W?4=l67E^V_8(sH8a4EK5 zu7B$!s;)(_x})uxAFQ7ovrJWyf1UqREDCI6GBx6y>Ah{uS63$d(NU=+|BPpcp=Y4Q zGy*FlE;1GuaajyvzW0tv%*+4V;&sN9vlg0LaHLE%-Hi47w}CZkCXYo!yQo$MT@exw zM=6F@cMRiYGI^Rjf4woqnL{{uA86m)V>UCwb52u5>|}pcS^m`}61#mGHCM&pX#fOK};!y}@BP zs%g=TEc$1Ml-3)^7_)YKqgd1N7I{6)D=lqWztG- zqC-LD4z6^3`n90}lQk_yY)#_d+4hBDToiDi0IkkZ^nCnGs50KlnD=rHeEI2HFWc!e zl{#Xb(qq(nAGe3Irzf}}3_JfRD46?PxI^mdI$Y;!IJH9(?b>{DhV7e`ML!NrbE=xYI&-lFy+2Gd<8t{X6H2KzyOH343n>?2tN6 z;%$6<$R(N-NYrO|by^QsoQE<4n47~{H0{;jTB2dGbyKkA?daK8?QQCmsk>L#7cV_m zI#+XDl);$_!EZG%-CtJ)qggY%7S182$cE11NC?@#p+9$VzChCp&Mry)V!2=FVolWn zBy4zZE~y3JHQmmVC>%vu>#BKweYTSj4eQnP|DEhYT#FroE*p&*LQmccT87Zylm)Fu zCQn#nW4i4joB>B__O5+LHT`pqfHEos1RoI2G+kna38h9uSd9Xz)vf8XDgob=Jv z)^>TAIQ8u~92hz;>e__9aFO;Rw$?E{^>au0Lda+WD}n%&J5o3U9ci+UcxC>iC6|oRl=CbCg^eVsdm!h7vjC!{SX{;evkf(y??+J9U zL&lBtZ!$__Tn^z>t$r|!MM)z=BqI}N^O%i22ygzRvYeAx#r7+P!6U89+^;cv)I^Y| zG6f&xwj-C*wRCGa!_Jme!uY7QgT&6uw|=}YJ~X^}8_q=e^tl|-mS+R+6*)UDdy@uI zy~R$B;Nj}fT5q!S(Zj*B(<6FnyBYE?vrP+I2~|$-`lu)RZ`|W|8X%gPnORyXAB}7_ ziENqLICqrX_)@*~h6>pP9SWsx>5m-Q^o4ThE;M(>XKO`_@)sC#b$(Hvj^~xmI4Nj- zz4_%L|NMBe^p{U#Gl$#75z(=+Dkcb=xBQ+}|8S7zhFE!r@tC*gah6`_taZpmq4%La zeU^jU^rK^)Vp~cIYJm9y`aC|z7#gI(ztivsTmmTPziv#iTYKEraN8RZLQX8!+UFcW zN23;A|H8OdZta-}R?}*KiA8Tv|L?rZ@wcYVpBxU^4*HmQ1^#+ubyT%l`_OT&qkmPu zs&B)OVDZn^8oiA^V41@9ZP^gbOo{Nn?sk`Say${0l zXD{VGnIF*_{QTe>Rsp>v7b5BS{^B|R!E(z?$mrzur+Yg!Uc%ZL-!E$3?S~U+)}z(w z1ACAd`lM6(v&$Dl>%->g7(MYEkN?+?pnO;!`#1Niw>}d);owhANttdH-w8jfyj2+z z!tTM2#Be7Fnsu;Rk8b*#{dAM6@i5G(xFD8`f%n4f$NPi6(f-a%qN#$j;tEv$Q=&_okG{T;P8Cr{(4DA*bKbege}i)~Xz z@byuLsPmY2DB?W7Zk#Z-x}Tk&jQGC z67bqUTHdU!^Xy-B=#;KS3{ffs@^@_Qk(3~exGF3 z)Vr*QGG_+|2ZvrOlor1-<`=%xij-*uhST;Yo8o$(Atx{tqK5tYu6YpXJI=?)qc}}` z2TP(@A?n-U=b1ppsguGPNm+xV&z<|@{t+-c{g#KXvfrcuVb5zifP99k!zPQM^(C|(<+^@q-G=jCg zkjZhwr0Lnn&eYXX@q)13)e~8sn(fb!rI@Rkt$vt^zkTXS4}X{DWk_h~uQytTTRt0m z{jZb#o%=s}g6<}NiOQ}L!qa)T9B0kPFZJh2w-89|fzn2scVton`NULkRVjwdh6iwO z*p1)+GBZ2md_>zqzfkUVKy!$kl#lI9M+w|p_H5X<|FYdv8!!6QAacCWwB~i|4{F3ux{v_5B)W*kk7qxRMvd(|%q>C+)_cZtrS036T-Ey?0JvD{$7`#rC}HomRixCdt^m)_o_ z<(dB|H0B}Q`6E6R2__V0R1jIKzUi^Ff@FIixiIgJWZ5%~*iGH8*&XB`@oBLZIa)5h zreLXwdXEryXn= zte0&(Nh-4I4yATH(+;B%HP~6~|HTxc)a$)Rz*UwH2P(M`-7@$KMnAeLibx;deBTRy z`$+syyP3B1ZXf?+Gg_`Q9D_G%YP;F?shZ?k#4t_{ReM$s)znUI$Jp!moSGM$c9$4` zY)28j@cJW(JtXn?_oqMcC&R-e%6kR*e%9jIe*E^b$F#Gzpb)|1g>ZxT-yayl?Fi97 z+(FDbV!J=QA*>@>#Xw)cvwz`+JZ{JQD?VeKu$s%pEo(Ip0m0v0MDpomB#sWgg!G}4VC z4Fb|V#X>+DL>i>KyA-6my9A`WdyloepSQmKKKuKz*Kyy695B~hb6qj6G0ri@c^(B~ zZd)a+r>7V#j=H47!jh}=;HHyN6CY*KUM^$mUG&G}Kw|QGv4&|pndk*8ga-BQm)~7O z|GK-W?BAOUr~;mw1|RG`!r?-Wf@tk|SVBH$-DAcsfk%7m@uE* zTE^5C+T4L_vz;jUn0Hvg;rNN*$m_5X5$fhQXlDm>qdymbJ0+_+-*NTeQk72hHwh}Z zR3}$ew|C8&h@4#Qj&ou>Y0%HxJ+O(N>|Z$dcZBJ$=hFA$AjKOObgW2p3WY=q1eDI+ z_T>?7xNf*HcQNwY%~A^s=lW>R!80NLPp}tQQrPQiFMdlCPxi` z>;ozDO$MG6j=c#BeQ2imIOrVy4SE=_EMN{vbt)9!BMbhb7jRLyVjk7w_0u4*!$h{C z?p|SCY=8evB&seQO7Pc5OtIKnNrrI=LYl0%F3Krdw@ItZ z{&ic|D<7$zpYPph)0PD$>d~`Fmv0;ub9jiQtD{59N+&L*uNf_lCG#<@7pLaeV34n} z;C0WFH;F*R)7acB>)`My9RGgs_8H#>oj{l!Cqz9>TltH^7=H9)YHGI~v7Ggf9B>#CTh4x{2KFoUJzV4~w?#>?g z?IvHJELIX1GxK&A-w}x`A<2zY@Vw?0G6SYPqLiaX>3icId(-hMo3Y? z>qERjsRl$Le1&a|&4D9iq57}?*+wrL$Vk&A_^X_4q`bBN-Wqkr$G|bq!qURYG%6w} z-N4Z*zE@ra`Pb*__!K~k3x!8&R%obd2V-&ugQ)3&y7)qD9jU`0`!_Oj9vBo`Ft!5T+}#Qh=iVy&;TK! zu9DH<9bLV+1JYG+O(;bN26V^dnGT*76{@CBr#;Fl`UBq)`EWMzt_W3Z$O{70OKRqr zf{VhQxA5J@CI^>nqNpWm?!&7&cR^ zA-kPD`IAgwyhuY#c(gR4bil_6K&{`k#`8%U1voXLj?$#G1bgk3G}!aGo?)ux1%!dbWUA!pN*>fWV& znNi zgdxKzcnx9Qsfgpld?T{sHX>{+ck~@EO~J`i0WM~VZADi1OB{Nn*S$tN*+2Afx{c@B zxkGr92i`9F?Mwlcclu8&d_cd;ys9KKpl719Nxo2s8?R_jYKh;MLUiA}hSqus$)}{r zelCs&35Ncc+{kFW@KWjQ5W`8w$MQ@WS!83QC;y~pUuSDZC}Bf)ohpCDKBU!TKuT%E zAy^h4+|9mMKk`e=ns+Id3YT{?zf;`pSsk_%BUO|=`uMeC&zo`4`6K%Ji2aN0oe#%2 zb&1N#Hm&nZ$ z3pUovj`6mf&UqGFG_N()b0mOJM9Eq$PtNC5pHa^ZlCqVf{R8rpq3;nK&XqOXi{3AH zqH+##mk#ZN=L?sk^R-;kw|RY-5|L4^XGp?sMYxCB%10^9`}fjkeluEAzYh7TU@NL0 zbm;7)RnYYNPYnIXQzuPRW*%n_k*XJCh57{Vcx8kzmgs)j;h;tN>ZU}x!+|yDEh#TY zt5C}1m3zFK%f=V(iE;<@&8;;M5OPcS=U7^7jlb$*f7NN{oV))DuVO|>_UXjM%pr{TK_jSY9xifvl2Z>!JzI4UH>{cCwxgu1A_Hy9IN67Z$DK1qSyi?fA`PeK`l7N&tP4i1&B+dn4f@t? z=?{^HX$wR25FYHZZ7mH5`dcZMvs#aj-;qRYybGiDJT-f|Mw$Dx^(kI$q&{A0OEOlJ4w;9ygswgU@oLk)A$~$R0?O^)zi8c3S0g(~g;L-in ztK|o4`VK%kBkAxW&?w@0?l0>_i()q2C&*r~oaj(h!$MrzmU?}#uBzfl1+{T0y(!t@ z^P9gfnEevT!cM{1^6?K;cqDd6rUL78m4Sij`9$$Ld?8KS1uNG-<%cO&6!hDro7x)d zZLTFbT4oxfGqTpp{T06|z=6v%+_>K2sI8h@EtY!H7Wkg)uKyX5aODEm*P`%a3C^Uv z*tz-c3JpJwV5`z+Oy%1pJNYeoU6Vc%?a_V&Nut%~N;VP}#MQqNW`~PetaGhd^B&|# zG^GqF#lH@&X*vD2{RukkdGr%`@j(6=|9$cp>*8Udea(Uw8UB?=nyG!S(f2VJ=g({p zJpambX`z(dMgFM7S|QOUUDj<+_GED9M4BTtzyA%cF=NK|$ik>9(LH5QhZTVsHHOuxeqWqFwv zi5PtXD`@K8b(HhXINFm$F|@?mh>kht6aFopIN0e+M}Bc-hfboMA)?lS-(&>oDyjFK zHt_Cem@zHh=b#cs8pfQp;#z7~3a%N-_2%Ic7KY=RRoLjslHLRSC80&lBTr?=I%hD? z;E(f?`6ITM^OK<*Z^9wdxH(T^wpXSu^~ZRrCVeNM&pmTt0M^fSN2@%_xy-xCqc3Yl zaI*6-JF=%Bg5MAhz4_ifERFVzbVh?d#St#8=pbXHZ=tf(=3s>U?K=ACMeUA%`Sq0O z;<X;@$3tg-Q))+-8qG$JsX}{U9&%Tp82~BmMfKK6hBALmKn0x zRY%vC^pjKHzOLM?*^Zalv0`T{n51$|Q%iF&s}rIEFCvFbVcyLoKJ{#|gJYiPt&`Ct zgk+g0C0IKAw<${q+xloJ+j7U8YOziEasNr;@k%+tRxdWW{Xw=eeaXsp*w&A`a$9?! zGV@kogPn8*sjg=h9)>EuEKU4V9^JFRVHezWsPxs~7q9L0Wc|aykWr4NF&F8L8|5hn zZo`vH`mN%b)U+(JJ0mWhdVDO8yP_guk!pmL=8b*ivW4vVg`+8J*1TRuB32i9o!en|FN7@w%@j+-cU&~0TMMePrfAF$0#*El$?c|!ASNu2Qw z4Xel4yz9GN4s*X)jtb2g_D5Vd^qM?-mXXXITb@NwueXE6lt8v5 zFwog(I7y*K;SBhb!^^xwfr0Y;?YiODwJHMaI`QR=@X$U_#!;r?eB=^g=h(b8$!7Cm zaNhpNLvUh(5IE>?adCCBmCLFt26w$o$!#iK2K;aNhGH#Q^@-{-$!QgcBQr5+FHUU# z!)bGT7==}FCniIhJ=y`cwo-}E&z(-Y;*kZ~e~YV-7`18~SpZWIMAc#^re=GrT>ye!1f{zpYeG`w;iBAQ3 zqvMO))IJ|}>2CQ9W5>QCB66^3y`Y+H6CsOF3Q?*%+SE&yAs>Ku>a>RDvy??$VxysZ zBFoQ1UmQ9T(u*vg$K0Wkey=IfJj&&nMQKVJ70QJrROfRh=`8J~U+<8$6@{!1j>})+ z*;DY|H~bc)L%CMzU!&$n`}|29JiSQ*!KmnK;AyY_^`)yXy$txNeE}j6kC)J-d*@x- zeHl%3NH0_Vv__2OObIRu(SIQJ{Ece{7LFkgF5LLT+1L)s;8X!BIU+4H2|c1K{*?L| z5DO&V_CdlHPr1He6aDjfQol>tz7wnQj|E}90o&PP2N(|WbtrKa{* zgOL~g0kIQGK*pjNcgpI|ys~f!Wudlq(VVv7t}#4bl^9$Pj{_Sv?T{yNc$l65^~Ar! z!2e!V%50K>N;(f!Ha!OLuu1EECN**fB~OQmiBKqk&oH0}SPRsp-+!av{}Z3WT=PFJ ziA0A~RZ^xIa>*i{fdK(@K6O`TEl~1z01{71dIp`lf(6giiuWI_E2VUHu|k7AT^ZkQ zodF9iBcoKl(Y-SzfzcwEp`HE_g}A}DqG4nd&NqsR`HWMsjOr29)O5{Z$qn?kQ054e z6tycIS?2@?XTEjgUTa&}$ljvd7 z!q(R^TIb!@oM^C9;HJ4(u1s2+zR#VqAf%rDaVM(oXe*c>c3i*P8F!>IQ#^MdZp>xm z#;pH4;=e&__@X+bO_DW_;sk=RZa?cz)lvd)uei9CWr%E#Zt2$}K;oX4?^{o=*=_bs zbPEJu`DdmNx0F(T|JKHUa=@4sTZvCjldIvd&kW!WBNQpk%@Ls&&1Jy)1eDDmh3C{E z{g{hi`M7_re!lJJ7~IstM_E7c>ie`sF_wmK`(Lc@=TaAGdANL1dr*33#T+R0 zrHV{t7+VQqex14b3<ckgYMs->DX{7cASH=vP zmV@^kTQw6yAB60E-Kh)Vejg5W|GZ4ab86k|V0_MGIL+Mi2|O3^7%w2WL1?U5a_ zAq~RivEa84`*Oh1;2TBT)q0s_ihL`zl7Rp48J$vEXPJF2`^&2L^Fx_FpL}kn)MPSt zer4dFZFxABk!PQ5)MKc5YIiw6ny<@or9$gBFuNc-u6)P4U9{=RT$n6D^y+xgc`mzE z-$<7~qPqI}#+yml@Blued6J59kz_8#6}S$YU+=WtYZHGpEmoo+JQKxov>vB*RKBt8 zXw>n&cz=6APRGs-e+i!BQB7^FgPZeaM_oytz81S_w?y#vVyX|{eo_6S8qzoX_Wddd z4Cv_a47qT*U=jG~ZzsNBih9%yUnfia)$&%U@~$Ewd0vyf5xcqXz- zIai+?IVl`-+1W0)-?$#3Q0Y2qKT|Fir6j6aU{%azJD*bP6>2#3xx->A(qRgLY~lP+ zVr3|sX$BcDV^2^}4poH-QkYq>AIexZPJy2Lt5iH`#g>0=6w}uPAGtP7cUFDO zOmZv~?=E`Iie4Y(YGU=rM?3shGo-f3bNs3)3QZeqPW?%^#c@=uta6!U>nUI9RU8@} z#lO<`KqfmVD9C!S>+y4p0lHRTHWmZTn{z3t8S>COm2bPkq=2xDxQn;-eLhyZX4o3E z*VMzZxy1B-h+#6Oa5}{{c&fDru>6cXwlVKpQsM3qpALh9E@`)$z(%jJPkHp3kBBZRVF-5Tty z@sBG2I2AD#zd^^v)yO^Se^1<6GmJwu>qSLI$-Efa2UX1^-y_q@)q?HF=u z8iqk(QCie#z;w*zK!cad-UDh1;$TYzG$GTS&^7qS_BA@CnU~DkU8to61%qmMBpmHW zvE=Qx;GUZt94wQ52M!n&Rz0b%59bNy7cx)MMW}e^cdVpsCcRGgR3iN4N02PSI)-nt zwXL?21TC{AYqhS4yrkE3@vYEksXqvE2;RGI*1f&Ny*VL5o(h?*R2>BNRz>9A>=WKM zEs34j{`%AUEb7)?7ndVg$m}$hYgvJBJl0C3vT19;Lpsn`Cbs(+&%0UZ!ey{E&t9@p;YcY8FnI4hUM5D1GZi_1tk>wo zyP@+|A5fNW@FsIAPtEfs3oLzyi@A(2~RQ~C@WKMtMwvf3GWf2gV_ zV9##WThc#)EYcnGQ#ucrYN_RH1};^e=$?ks4P;*FH5#}t*_ln)XfvaW=+Dg1%oiSW zK8WD5GC;%*#*h=i(-~z?`qV9hD3dLNTXR&wP1O_qgirFltjq~L>^aKx>vlY&FZiwc zeQ`t1d0=dI^aG=AoDs{m7aF}QxPW6SOY^bkiY0ItttgfkdS@U#Url zMby7pFPE__D|Qr~uyQ5_=D`l(-)LsNC(6mmsq5VrJy9&ANl^6LLNatSN!BNB-oT;6 zW#GV*?3U8{^kqWMO#YKHaReZBC7m+Z3UpVND-X7JJL;~t3|%wS=hz6kt}ruVJGPlx z&NQ^NJmT0meC!OmD`f?yE$z)j(zAVIE?wJKC%MC0wTH&6cOoEhSph}9=`iOqZIQv( z4x&)g?EA-ljZF1@B$+4T{@svXKXV}VEAkQwbtZghskcw-1Q|Nmcf}T_qoGL_OE~Iw zi(voW*0;Y-Qrb+S*wkK{nWr1^1zVHfMb+gG2W-iFvpy+U7>b$V2eY3@&dvW^gWx<< zx9R#|nZ0=S3h)|1e^o@xsnZ$0b(4NSUp$KUs+*+A?)4vKC8ql$)^vQt)CGkvd4D$Y z#l$6pMof`D^cu@{7HUg{O#FU@M}^sgn~<7z^g^#rw)sC$v@?FbEO#WD^hg`U^rwMV?W zV-w{!ZrKYn`g6|-+kYpn@3Hy{md7+=EF+yP^pR$trmbZ1sl&mAj9%EM`M-06ggzlV zBRip7x+Obq2<@O9!nRL{I0`7I{QtXLFTTU)!=8x#-_ z3vG~3*4Ew-Gh2MqL|!GE7H{btEoecGTIM=9mT+!dUQ9)apgpG_BHl`EKpl^zyF9yHQn&vr>G8T|U(QL<3? z*vL_nWNGBUQxkER)6F*`XcvO}88;}*PZ6ECM{W{kUMFDr0l+Txc8#)^1#fHjwa z+iva67zg=VWYuqex=5Nkvg$DX1ZyN`c2f!2RGUDnw5Jpm89Rh z4X36x)2D0ZC)6V)ck(d#n=|3neyR0WMbJ#R>1UZ*`@9iS?k#ALUGU~MyPACAV$k&BL6YzjJnJQg z=SWb?LXXmkTvvj0N3GWwdVYX`nwV#5l%fm$VVZU?BcWO`b)s6tu)#kb{-7xt!ZNU; zixsFe(XkVNSzC@^5slYl10Z85I$7%#JfPhHUW0rbxz;xJg&XD20A^%3+`s0q7XPsX zX->MbLj;`n=!STTScHPzbn#5fx3wZL_n9+MQI3O6IIb`2o4Hi?Yi>cY=Rjhum#Tug z5EK$ME{*0&ii&QX<~XA9k?f+&EKB+Menp{4ks+_x;67)_^@&0T zQxUe;%4d5%J#?#%!CzwPeoL3x{)MOuO9Nl#&Oh}6$iE5(R;si#&JH=qp4I*BvCG|z z31`Ns_;?=sJ@<2186nu5PtBYgwruOD4j}6U?ssp>%v!2GD{mrg6 zP)A5sGb`sr=N}IR&{5VY{cPpU@%OK`UNGARxa`l}$C8z^o&@msWmPpx7}65T{3-bm?* zqyk&AGUNVra@)MWAUNaK>eIFUCw6}EpnlH$9E8P>Yx|C{4t&S!_Gw5lL6%o@a~eeU z$nAO^9leGZ4|co1IzKbt(AQ8D`J11oprE%X9TwyDzYdY|8TY#)os>QzipEbvUQ1HN z4m0koy}4v{yicdFZl}X?`K5T($T~Wx${LC#e96+}7nS&)#*OgmtKc5JpaBzHD??O@ z%wtz^DK|OD+_Hpz@yHp32(a-M(q@!P+o-p(EV#&QGhlL=CCpvIRaQpkw%c9l5TX!3 zli*7Eaf`DhY0))baUt-1;^g%xJ~6&Y`$5C&Rcdy2NqhUf;oBFI^dzWa;h8=126WbM zPB%BnRS})n^}az)PAB3mc>TTTSqmD5tyyx?jX^u)d3$n0V^2Ns|-M6Bv z)4TD7FAzmMzjJi<^xuppB-XDRe;D37%Tj`uGxVKOEjktJ(lfGH9nZWyt8>y4Uo<#5 z@k74&54`ptxlS`cks2oz*zzh+=K(cUt&8drun|f&1(#<)f4G=GtoN9ql_E*-E>ok$ zPP<*W4l~`4eD?8Cs2GZO@(~x#6+L%X%lhqn?F+U>0}h(8N&lhA=g`2?HVM7ha(ku^ z4}5R0?O4yP7eT39OV>Qpw17u~WJC-U2`@}a^R?$pXU>%s%*RFTa>ld+@#KkCMnp5%--)BfbXls5Dey+t+EKMQUlPXF*h)s^uq$~;tpD);;MMGuc0I)r`67vQcV{9Rx(^p9Iy z%rQ#|cBU9sOYgN%yrqnRs;b^~`P~iZkjTP*`v7tnYUg5w@ZG9c-YnUaN>A|7ajUJZ z9Y>K~qQ?NBRZncB3XG_z{$g?NUhX}9-GVs-CN5rm(C^n0zV<=-HMjM$eCs@x4}d2K zY-kskiZn(3id9a_=?2?pXYFN^hOOER{F^VcD4Y1_J3S1>dMl%wFHFnN-?p+wC2=zm zdRwXafxgY=x~C8{&T)C+p2h$%RquwD)|dX+n+69uFWO}hu)`0*bkdp`7wwB>t%lcr z5TJVAHs~<+U6ldf9T=y*>GR4Ng$$ z?}NLyX3nF=AENIiK-j-|bp0sHy0mQx=`Yb3Ge!2K<9pAbaLCaMP-Y38(Y`{Gzj@qk z>JZ5|>s+jpjJ;}|k*CPYPZO<;=N?k`Gc-WP3TqHaeDR@#e15}ORzW?RF+b@vn8{DX~<&z+2( zJbS3IA-(dbjX^od!8*WdVXH52&^Gl?T~v0NUUjgAI6fNfdv8jdlvMw$ zyhx{*((q4OG2|BXtye5!+MElFM&-*(Y`cOR#tb%m)Syi2z4KTkFU~d!Rrv;8EE9dE z^0-3U+Nfu}f+MUYbiZAB2I zFh7**7Rqj@9u;hew=NYF7LhuZ=fR z{piN0-Kw|JS=gv>eDqxZIKQQ4>q|*^tq2Mmt!BLXjHh_tOBp`2*?o~-I>F&Z!c2)$ zz(P>ww12NA;d2o2ht{u{LH%yXvd|Q0n0z_lx{Nr)nj1es6_zxk6pz@ z?vI~FHNHnfmokWu81DARW3tr?xavGt2vFm!==UUThn~c`%kG$D=54Rv>AykiWY+SD zDgLTY%V|_J#vZ<<^^FLK^(?hggsfGQiQ3IoOHyyzq>HFa_?Qt>7_Zu%Iq0d#i#Qb)M_%8QsNpZ<3;a0Aa|(5c!KHgX)e#fUUZGXT zT!&z;dwr9_1k8aeV{Fw;3JTrNI8l42Q2*2dF+I=rzv!Crdz0vkth{=3DthN)5*k(S z0Ell=xE+xECG#Ow?#1(>{;W!oPHytP_lq?Y6#mRHha!}gzt(aI()?UA&Y@BQ;o`dw zFEd=mnA@|SH57?mF-g>=W_OkE>fXM6TA<#X%ThP{=^4mQ)L>jh`lmtzL4HBkXIW6F zpee`>04wh4|G{HFTpa)}8*6dgX84f`l2#UTbWA*7sBJ zfRNors)E~XHa+CyVenK8vA!GPaYSKm_`gm zS3s)Z{PvyO6jf;-e%VC~jnhK~o*1V&H8?B+&;Ek(z5x-bWo0})sZo%_k&%L$;-KmY zdD9Hm_IQ*)J*I#xYqd`+8Op`+tcV^-W@j_+FRc23H;uaEhF*0L#5Z zZP@}nG%?oQb_ve_f}rN~Lt{K$;#)>5B{ zJNwmpqF!MLo1(nDe2@e=N@nlx#?_hjPyj?N^l-M?&F92>S!Y z?;NiNa$I)9O7ZC2V6LN&eC_i+pu~5q3p*NCM}J{&(x%mlN{1fet)(t4N#ch}K4ig0 z1D(@Cwfw0rr@td^_}c5nEA)85%9_D z><;Kh#Y6W@c<(0PfyqZM+vPauKu!hm!2QKsLSl|X#Mx!_r<=p>L_^7Pi7^`NQaz~Q zV&J8DbkLd5uA<-52UzpM_RzWH*Fzg@0E~&Hgvld%5$wiPkt~{4n`^O$3w>to0J$Y1 z$ChuQ$tQ+E&80m~)CXPgT)ILwwe$)?;F6HvT>TzWFKr2X@DgYJQZQha2@Rvu-tStZ|K@vB61lg(0HFv9T>wD7Ff zYIrF{M;xs-jy1mm?ctTWu!k%5P(vvXh;smRQS_PKnET;+jX~>J*v}la2$*ccB3Wj;q4V|tuBdG3_6!{ZyX4YfVE|>ia<=TwsAD1J+)R|M z;rNdQk1u@%OZGzVkW!}Cv|YAiE=;0E9c7Y!6MB{%nL&8Ok!tt0P$+LqaizR#)xyHT zR{3Gz#K#Fue|1JBza8H7@Xla(t0z31oA(m?@y~1p4zM-@m_-z{IR5iJK!ToJRwmHS zFXG?^oaRaZ>Xrk_mBeK|pY*Tp5@hoDZ+l5+0!8u2@_)s$f`7j7r34~J&F$G)F%Zm^ z;lLPmnFhrSo@0{(8aXnBzKk38NP>jDyg$&l5sP+4ykBky*bUv9k!Mju>*YNDI6m$c zJ?2TpfZQ%`*>g)ZS1;YRANohOMheG_k^Xi+)ne_SKdm8|r7I=UE6D2e_-?2t#l!eE zX03b&^byY6DujbG`cWA&KylauG}9E-Y7osqqsM%%w2zh#vek3aoi`_kh9QWVwe!>& zgU=BZ7uG+j$V;Y1w_pHo5*`tc1h6)Il^Vub{dnV#A#f=|A$K4G`I?lUg3CRDGy!z+ z=TL9WE{3RxNH0*N|8Ip2U;hC%ZqBmEoKg-(JrNbs!9W)O1Rwuj#_Dfx17jfp!{ugN zTtt1{PHMrW4JE_yH!BKe-GVC|>{z0^H4bW85R)jr3%nrh_r}?DF;Kx7z!PvdF~%N9 zHUASOkdYGcjuR#GN$dR#d0W(-x?7l2pZ47dSvpRDiuWN8sRU;l)b*!vq&DlxMT-m4 z;3Bx{@NrNY@86*#AJ{|XS#xDJ2X%W2Z9)Zr<%dALs+|w#l!$I@&R03Ec}2J3#r~FO%zM>wk~DC7_c3t?w`Nz+O@`>f;mK z+dJD>UoQ_lJ^sf!MQ{_0%0YN*nLzLdS>^Q09`5;ccd|dxU#|$o60pz4J`42=b=u5u zYJa+N)%Z-wHSmGX%%uDm6bGIE|9?H0|I4{7ARH?SocvHWjk+WYYfFMkF1pDVz0m~x z(-gySaqY;U0RFhXtxfLf2ZnatXXvT9E&3+=yv3ti=o9I!?{D)d$o@0`$KRNl*K)qy z1+WSsL)SszoQL9f!pkoezVI@($79|v1G*-x&>~{Js2qCBTRr0^Hfo%bF<2a5B%`xS zPM1R{sh3FqTnQ!9-=fZ+u{P-rP$#b>EF8+>O&ZiB`2!mj?b~1=`=KYHsQ)R%v)Co! z6zGSJ{@nk~uIB6Hg(i@XM<_+X%%y!ufiGkDh=Ck^sQz!qEd1%;EpDC}IHcuqBFzX; z0^=CqB5ff(o- zbSA9Mgj=?D06}Rco38Lkg4@6ANPQ8OgI zm@is}@&Hu`jZR0D+yOrjz^a-MUtOVW%U*FP=g@a&OS_ zht`y$B4S$e>4o-*BUi=-4A2wk7m|UcRH6N965Voxx^stvLvwG>zOh+?z>4My`s98l z3x>#~pdRh@3p~pnps=RD`=6#!Dp61`_a54yB9~<8zv4_!b7Exl#p=iWDhVwAtv3*c?a8e+G-O0w^E0qAFeKz&;SlMpEbT`)!A!nrXOFdxHD-bLna|=H)C)dJiQTY?%a%3IV%3J7y9-Y9eiPK>B zuTA!89s9!F-ha}kVa^urKkF!(Gms_*g)n4S@He?e>lasO7n zQfNI(X3>w5lO`(^0WM2I^m9yOvJj6%g@p^WC$*ubDag~y^SL5)C<4S4h@8gwIh3Y_ zY8@TVA-rxJSn4=wgT8w9Hzx=R(KvVB7sVK;c#9ghXkOqf9sbkoR>jnU{9eU{(cis; zM%ja00_J(%DftSe!)mEZO3A5%T+bJbQi}v`e?bvd@&_J%VYwpl3@3ADaze1kY1wk6 z>7IyYacSG*RO@Ef)9%RUTcp*0xpHo;5g$|q)>>0hSX{e^W~YbOl#IVW*#zv+0>)V7 zHATC>zugQ6x+c$!8K!H4t&+)4gEJyL;}fdFc^&Z=D)+A6U{pVY>P}a~UfEi4dy7KU zg7vKbbw3?SUS85DCl->?(NW(f+zK6JW<7|GTB+g|i0XKxSCml>i~d+t@tYmmdEFfG z5F6NmKrV!wFOu_u=d15B$+ALWz51^)chg~3D02qUE)c+4p;pUSwDge`m;=s6mF1|D;=fy}G#0 z5TCV`)obByG6xz&^|Jav?HvNVt4vu-aqe>FleI0{csu89uUsN2y+-U+`(UXx;*5c{ z>aUt3@1mkHye@WCJSwSg7nw4}J{f|_+g;&{@B73xYZPSy-}O9kfzxH6YQZ$R`4l^EYlN(QB6BxVlQBP*6Nn+Z3e5XwGMBDlYd`>)ZEk zefQ}r1D@~|DA*M~f*kN^6r;u$wr>xHyKpV{A?LXze!iX_X$n7mQ#dCk|1`B!=PtOLL1bm$FA~T zF|}sC$2jXa%Tg=EpL~Vts0;* zW?l%8UK={Wk5SD}LA~Wae&p3xy7Q|_Xrm){hJ>oe{N(twor7ZiMmDt2e}D3E{kr<( z2}mUsBFx%T4?fr~zWDY>t*MW;i6togtJ%U9RZgII@bAmaF5f%y26FFim@LfCdw7La ze0-)8#ykiDQ6PQI6i1mjzFdA_Hxp4P5xO#%zmO!KOaZktx?9AaBqB{{Ee6l^jZ`n- z;F@d>;rB{FGjPBV@b_t^_JGW|f&pPCa=n862n3sMy6hXWqQ=qHE`(;}D1q+pHA%mh z-a|2Sby=zH(&K|H?B1Gu>^rYbtW?y%OlMd~?_ zOL2LW`4&jKCq1vmepUCS4rt9Tz6Jra^U~7N%~}r#dQy~XHs{FE#HF*t$R0b+ySG46 zO*FVXWQ$@6xd;hO*5Xf~o}=ASWiY$tP|a4Z=_7O@7R_>ZA!F$kY%eyGfc|gs=;y1< zJjUbMV|g`EmBcecXQH+|R7yTU$`$j~3z z*i$A0eDwKu;;TLFG7)~0f&HtXzVvw&B=qb%&!B!ZT)mI?I+VlMV~#c9yLfQxXAf0y zN-UWLLtIj37YCxoVaYK&Zr?J<9Mxl8fcL4nGY|bfMn9GvXWd|JN7mXRu#9++b1!z2% z_ojo%wSpr+hZi+F7|#mculp}Ice}vwFAIGc$gAHU*7SPD=TuH@+;H+fe>q@vF5oQ` znVe`JziZpz)i@EN4r+8N6nz{X5wQ`$M5TBB@aX6g4EmYJ0!_Y3Xd4ONwsm&hdwZ$+ z!R!5Nx%1BZ?Cnr%c!u2!vi9EJO-Hz>`gTnCKbC(r9ofg$D79u*kGwogag!xPt^#4t z*mD5oi9?`ifHHTxI{aC?3|3&e|1!v1dJJj|Zc$pV>DAfD!Fs=38k+cBBMy>QoKFOY zLbrJA!8qU0YG$WD>5R8?DtBN2r(92w|7lH&!h}cj{R4TqxWxEsg$bjcP?L`nX!bbz z4BIG8<%&bPMKJih4rSg5IkHOx1eEXTeTHa4uJ~?POdJYr&aQEtdHg^kjHPB*wd38p z{Cg5n+m|_B@1NowTXgMBfARpRr>z!@oYI{h_r==GZxZwE&t6^RH@_t6?k+=p&46UE zc9DyRmu}p1Z{vY=BOB_+jg1+VB~?db*tToB7p<{~Z%QKDINzlauX(+l*U5cvIgc*h z+~vD2Qulj4DAiz){!&cLYrOaPf9~-e=S+CMRmUS?X9N1zzT5?5t?C|nk%7|C>eQam zXc-NM8OO=z1G>zc{5z7NY_)*jnsk<(KYtQh&XYsW#AK!x{DKu%=`8RYNt$NLCP$l3 z`=L)iih%AUWa9mg1j#JJt{rN*go_~6>oe-SPF7YQ_%{4Txh4Vc=GlVmZ2@V3n*^bF zY1MFw9`yrKo-Lu3CdD~+TRqqN3vJ24M~V@2dr&{UPSNulRuXe|$E7nd_0Ihsw{dVx z2G0ag2`iVGiI<;ip1V(>eT&QHVjUAU!9;)dq%u;X!+8$>5Jle7?0E4^gw)RP4I=HU zUlaQlyefu)bp7$h)bv1Cs`cJxL~!RV`iAXmVjoqs`jdN%+;}b8jL|AX^|TN*5lBTo z`pCo=<-=u-70HdqrDQ4)$^G!k%BC|3s5#gdbj?`_S7LXc2V<7_rK&N!zF4?#Uo9*c z3BCbpE5OlNs6F{f@+PNtUV|&O5_oiLP-9ySME&jgYQZ;B>IMdipPo6;FVuzlqUgSb z4$7?aSYhWVvJ7v-pz>eQU)pbmnj+9WZ~^%SZnSe{R;lJzu~P9KDtV{5r6GVcU!S6h zorcn#jCS3#@Fb9$$NPz>$99bGt_9#EO_~`@g2n{U=@YqBI!qfyI!Ew1*Us+9-ItC5 zS+bA40`YRoE{r=!xu#j}^9@4jcO^8ouRykhIkc|h0mjKvu$u3_Y-%d@t4>Qes?cF( zs5S7dda~@jTl>=wwpaKKzJadKCHQ0Q?DJRIj(udnAB&uA3PwKW=r$@_mq!+8Pg{VT zqV|(tl=LSSXKTGl@RH??6v-zhmhRwSH3l-g|H`11ubRG1k@=bvxTV5*-r!cdc~wP@ zIA%ZMx_%n_V(lM>p~dffmT;!OnWT6yTOETY8Rl>*ynL{|{1A7zZ5|w3H2?bL$@|(X zke!ef7TZIC8@60g9@3 zfio7?d1<@u$B!Q=Y(&{wBYAz`_aHOx`B~$NS5}scU>Kac6G5NwR`<&GlnWNcB|^Ap z+L)q8osk_<9sQZx6xtNtg3*|_@vqS9^dj9U8 z8Sj*pB$4H_3WKrlm=x)rKB=l)O;>5NAOsT4=h>zEJDi#i4+5Lk`ecSOKlKFX+P3k5 zp}hdrg20rBaI32G061k2^cIt5 zDn5VjoEb8EIKHT*&%CnHhHDZG3TL*X%Wk?&ubu<s3hrkzXlL{odasa7AzbuVQzlu={uah3@s)kD?*w6$*&J<<`x#T1%tSPnwCLc4YQaxHGxi7OVEDW)ldWxUu&FvVn}3N? z8;9a&?>&|9+gENs`&Hj-7}K>dp^}Z1rHi9){u^^5es_{jQ$CfPOdFSw6s@SwJ!QCO zQG6AmO1ob`F8kpXW@KpJ!V`d%RSO1nH2K@;Il#Q+t1zK1nou^#{9gh8(|flt4m^5; zoUg;I6u9_5FGc?QwJR7_T>SeWujWn=0J4MU!P+lae6E*xcN-MFi9ol?by!6+j*pD* z*;80Cd8!dL3$efNUhxDU6JNS5r+~3$`ESH6SZsMfmXnh+v7GTizXsRb;&&bCcYi8w zwfAfa#8<8?18Z&d!aYPf_fO@UBXSg0<5z~$A7y_Y37ii)-~xi(nwCu~`PAjt3;v75 z(wJb2>*mgL_FmDxfz-o3~VrEh_zI z!sSPY{I<(^Sa1Z(_XkU^mDXTJ4u9h3`==mxG*EJpA_wc?!+T`Ih_XTH)YP?Q>|4MM zex;!yxqz|S^q&EcNzILxp*7JscUi^t$sBHe$?Dx5)pYNJQ`iYIB~Ma&21_oMBLCd^ zeP8btPO6aRhAD0>bN=xn9hUnz?_MmhEMEV&)AwIy4?o&jSp@h~t9x~Qy)ftTWfd8S zrDKsyHiLJDU3#|}7+QMcVHwEQ3_)44a0q*w|2@R>GVx%q5xbxBq>Mj$Wu%Ohy+Mb_ zXHcJyx$!lo^#EQx;M+%|D6*Tl^8NeuyZ1%jgR9>BJ^$+S4mh#y z^74+XmMT+z5+Nc0b+fSEt?y38dxIrsa_*2}q#R|Jwvv0^?02(&_Rl!Q%JtX@v3~fW zIm|cwE9}h6kwIh&^%glfA^^LOLOu7q9t*)bC|#dIsit2C0R2g-Z^3H%oDawA zs?{#(Ft2^*eFNli)2X*=g~vY+jyws ztA_I#1FQCyCF~9)@%9wu6Q?(nAHqeLDlV0iSrq3Y6MeL!eE$z^?;TFp+jfgCHKK+@ zZy^Yxgy^ETM2qM|NRX&O^gcxwL3E-=Cy3sO=w0+Kdhgv*&a?Qv-@D(vzkSX=*SU^A zL|ALhIiGspV~pn>F?aciE$hZ^4dtHB0n;Z@non=Wir8P4%Hja?9c);I8=NP{5e}@& z-}>_GFS#Ls$5P52@(7@wJl>oN*}+Xv>g0t1s<@GNeVAP6(mYQxI2o8(pKw`6&CVDl zxqap~(?|hI0T5qu*$TX_+rQlFqyZKV*rkx^khr-ym$q-sXJEi<074=dFvE3k@Sjb6 znHelX#IB19K_XvBgvkJ&sz2BZy!U2-3-~G4Qi}FrPK@xy14J|hmW9S~JHS!nkk@&Z zuB$f=4WYq)f{?7(NQ$<`fDfd!N~ykhEy1KApqTvPD@%9KgbiN4w8GFOC+%`q%R>9z zj|*)tyJCd*eUfH?UU+snv(DCWcqys;4RE$#uc6>yK&0U_?KaJ23ViDYYWkCrqF4ic zl(Sj5?tpQI=?v9UwT4jMf}LNwY+bd2$QlXO>Mjxt&en;sMWZmdT8X%xXkb(Dk(8RA z(h4}!{2uqwKRbv89&2`Wr3Cy%$;nH`BW(8oMAcs2-Jjk$yS?ofGs*}MHIKHoF%kiD z3@Un&%efC@MTWD7ZS{Buc_U2z1x!CaS!dy zv)ns~+;Msp&ckDQiXYDK!m+^!UnyG^*w>hW2yd&h6*wmPfmq{#P{RCIO|k;$W?Ft& zt7;*cSVXm_Ru{E)r1oYjK&jvMUD*ohQ>R3m{qwSBUr_|yo2U;aq5PW7YS^M?)xwoX z+buNiR=^Dz4Q9B(LAom3yaONjB+5ec;D?(aa3jABU<4TukDi>KrYNV&@;P(i&Qc2A z-!i87#%=j|zE$z>9Mmj_fKM;a)>$$XM>Xd8|OJVK(e#tDTDw zL~sv|AYc6Mq%rvS+MrpIdi%Y@1ay0&Pom!KNnhj+9TGA)Q{jYF$RXFaT52&`?C`K7Vi%Mw8O*Za3Ss(mKTS<8^}*46 zOGtBSnjr!i2SBw%>Yrq_XL1to-@+A1Pj^pLrv;F3zL>TP$;PaYX$Qzc0^n?)>Q0x- zuda9s9DsZ5C1@0kNZ`4B3K5|^PfEPx3T-*_SXXAh&w&Mozq z6Q;JJWD}q521&3UF4b?4QWn~_TVqq4APIltpV}Bt$eSggMdykvk^3p z|IeTHBwWUSv5sFry&$M5pLt{aFEIfKhLk@Tw-#%bRSTs2boIicUT+y>Ct-PZw5qRqTm~s>EN9Y6W(uIk0>3> z^=6(fbI{)@u^{x^=Dh$OM3=udCmeRxavLhFE+OQ(;1;m0`#A!b>Wg$I+lE*5e}M+~ zPEQ{&2jplJARJv6Pe;Q>-(gtbM{L(1*&%~@$CP@!0TTJ0>{%~iH)on;MHtZD;O41g z6>4G!Nqfz=zMxGGt{<}#cG;BCXXRMzUNySlo%#-w}9it&6tGwv5SG@y0aqb?A-X&p2l#{D^FJQv)sUe6Ik%|K!4H6Lx`#D=31h60|w9cFH85ok- z2I4$e^uH?3qfs z&~bwswDOaN1jzPCgw5|&*>8RU>^ZG~0D3L_fHU4nbiv<#b22FJ#Z-_uoqzJcx>C6! zrKA))qt!GJH|mcSX-dkpzFF>>LJ%*s4~$aW{2l|Fg{fr9xAxIA;vd5;cMcAkpwE$=92VxN43hHV@3uQ)I1HH4@yOnbTlU)6 z+qZyW)37lXia-De0!V&ome-MMeGF}?^GOqbgVT=ewqn>|;3FsLV7!Yn?LGc1X#mkC~z#6RYsGz<_|7hbG zK%N^`yj;NG!Tw-xJ`)fVLtkOPDKI%XxidUb3p@`QpQZOBgAKzXqo#H9orvf#WDqhj zdXue_UR#C(n0AAzQK&(&ys*~WMfW-%`j3q1e+aq%Z$#wF`q0$yGiu?z@|A?g-ToG zsj5Z>@ZfCLW0c^|_z^Larkix60pzI(>V8dr&&$|+{8?6J8|g#~{N$HZjb9aNq9&*x zJ5pIQ;!)8dI}0`GOOKvEGk*=t>G4D98Smv=5y&O?w6{Q0-f$&m!msV`zCLRzV+-={ zO8a9Rn%?6n8;KsOiAo$xp`2aPcWP)&sxr3T4AInL!+Do;g25fdEaT`*<2!@4+Au{dT&U6e6G=hSDUS{}_qWXmxkfNe8 zU+3bTbt#Z*OOIi`!%FCFY{Xf^=1%h@Au|q*I9X|T|Hb*~>*r{&eT0YKvyR0kcHg^-P==Zx4G!EzsJ16XYKHd`$O|$c{#XtENFe91ON{qXkgRZfC3SU1za&S%^wt^Hpc-uLF z%9%1`@@^!T=QC6>y8DkFlGD~=fV4e)87Od`g4dm#2)J!Qw`5B)^=^6UdSn_og)b{q zgnNvRruP|h`IXliA}X-p;Q&?&@|mWg>y(g*ckfYBgpeGaB;;i&=2>`;RgqRXQv?w1SaTr zCMWea2t4L)B63_A=E!87N=`=ud{drss^9Uv66Z(?c2UNFwhTvE;Usc{{1h61H2_iR9m~MeN&B@@LvAY zYil6zQHH$tWYIXg_^b=mbDe-M3UqVvM8-r^yGU6zV^BWpUaFHU*}tx0{fvz*#H)%a zb175ABvpwqPdA!-M;M{bSg;yg!!|Tb1Gv{MfrR1VMFcG6FR2f9iHZuw`_Y2L*&&mu zJ0=WYVv+=N+@Axw;AYm+b06YM(lT{K?|aHKKz^ECA%c>U^r7mxV(L+3**SDs%EigH z`;V*fKx76Jb*^HP*`oGyiIrFPHvJF+Z6ez0ZCVC{1e=Z2SWzNk=KJe@)nxe;7>LZq zhDpaT<;Bch`3y-fMJeXq){#N z=c~iUYc}nCxc*jgK_`419qkrjq4>l<%h1=v(BQqr8U&DmuN4s2kAru2JC3xav%{(~`yXqkU3c*7RW&Zn* z#{VjVhmMqomcGQtn-?#G5e>|=i)9s#yl-yQQ!$hK!_LzLqGvChC^qEC5{TM*yoCkt zirX{%UAyGlxr-l(tY7HuAj$;00j<(M3va$`$3E)qTh97)V0ev=+21_1Y{*7eYv!yV zhMVH>yd5IK47t9DYzPZ<3;TC)1z4sGd9J&hnfqYeQ%068fZ}5UXlU1)fZdsIPE`JX zeA#z>tBpaEcz&1Zg(IV$#MXcTNV}AQme!i$<_&8frfP{J&&lUn@}MsnS8f*!%xATp z32@wRgBkbp+1U7Oxq8w(u3GgVqmL`b6!Cz$%17XKJpa7iZhaJ~;ZxA=AEW)CjNX$C zn-b%3)P!+A#M=*%&+iN~qxylkHKe|0X<`d}pBt$#z%V29bVhP#uXqn&9PNLt^+GZ5 zKdL1g=jiT%g1%q>F7cgA;8UD$GP+M)rfPk_gu=*&kFUlf|(8-H1r+MG?%pXhDJvEklNJ!@^Co&RUcIVJ|Nr$^{TvLURq|y#>>so z*bE&odz|E8;RBB&gwNrM;XYZ0Wm$7fLq*4+-@o!!%2Xu=+$Gp;Ms0Q;gY(ydg11@~ z6Qsdgi|ZKg)6#Y{-(2v zCb{0V6M^GRYgLd4;?F!5)4mH45wxW=0PnT9Zcs@e{3xR6>EYa%KER2{rk*U~} zW64W~W}z}3GdJ1nxWVs80!m`_{rD)sWj1*S{7AAB;GflYb7L_1*bCBeP}LgFlLvzp z_>@ZJ{#}YWT76nPes2l98``)!17eNsiJJCA-ydi_>DZG>M~r1L9>?dCssGp%e# z-lwjH4lty=Tju>~FoR@VIxaq8YcZ4mYjFV`uHMj{RNX3+ShanT=;fF4$Jj~kDd+>z z>s=wzc}l=uv^=M&B2ET8Fv;}<_^uD8 z`zIb&&p$uir^F|g(9h*dt4Nq1(9-@xG^k>K<25l69LA%T8|&{ zD>j234CH+#fw^7855R(x*=j`H7XRTj4xotoO}X!Uyz|HP`UXQKzRE)BZ5_&u%~Eg2 zu{n(}u-m|*B(1K_kf!|bRrK3($Lo&ZM-+=SD601u{YNni9({MZsS%)^m9v?j~fZE$X z&xMBWKYDbRDZ3qJtHjLjZ!C5)5)4*&iqHMsC|X*vjxn~>{9{^ukr54h;IaLm(fJNO zN7o?MXfv$YG5{B=$K{~%j*#b1{NtFsD+<7 zbK)J-9Ay<9%Yk6t(MsgcaQZ0g^*pi00D<-Isj2SacXvV)KH8|vHC*`ll*uV7&I&j6 zR8}&D#>bof7kGOVF8~$XM~GT~`JSu>P$(hB+s$3Q-8s{w zb&ir>GTeLpqyn$dk~8)$su;+h>;td2EigX*#RY*mz&v}i(tzK}@?GB=_~mzk;Om#R z{YG%#l#POoxe&1#&Ss+kFb#$%o2%quniY>R0RsNnt+LX*bXt;L z4DbdlIwbD^mneJv=~=4PPVe2k}z+_>uTZn(ijfoeUo!{xJt-!&Y_y>hG^rg9@ce2a9cL+ua$Z!&o0C8#(*|G=nl$ zN^;=Ety!T4$vvtfBg@5hPX+V-7a1W^i8d?HHNJD4Dlxh13oPQWVJG0sC3fbe46F7R zZ~?%gv4ZyDlzF@D06RHbFEyUtP#fIC`L)nK0MPm26#`;NWm*oc)^bmI)IBW=ft4p= zX>R>5sJnXV$U5+#R%&Wu5nE7Q@@!xcbIQ~)8=bHF~{a3OJnVFzpr^dmz@~KwbMR~>pV>(%dtaxP=T)HTw7?W zW1ayq%j+WSXtrjNk)@qkJr1kCZw7M~16NjjFwBTQ10{_9V#km#kJl|6DWJ5F?1>F( z4}XYwYYc~5Mtg|J`maxV2#Mri_IyZlAaTRY3?8tOYH9GQCnY#QKWk!?+9vrfG0Y3}f4Z^^eX6B{BNl-|QUI#ZXvY5n-h(2O!`F zp+^L>l#Te{$;r4~fpxJXf{wc3JwlRleR1>}00A<)_U-tOv)K>cNDJU-=XBW_!UV>I z`^oJmB4?;T`TBNuP6`Z2L|42YQSXB@utQ5*GV`P0R%LeoP`Zyz6I7?9+NdYv628VM9x!2!qexJwGuYsrA!6AmZ` zi`5KN9XnGF+r(S^LP~FsFdSVT(9M@E8lgrY2$kz1A!NA z0mb;);mq5=zwkUB$_!T7uX_T_{T2+h9K#nZVVa6>LqyE}O@B!I2RpzERY}*H6lVa7 zWM)`0@VLS-ohFb8rjPGE(t{<61zWIx`b z1Mar@&91jLTA&shCJYD&ct4~*3SZJf^u^)utJ+;grrRQ}R}Wyxpp4?4vcUqqv{a3yug|R?L$7SQ zHVf~LBCi~8cfw4~xK z`v_hB2qb1pG3w<7(g&Q*8-3oi>0nQ9LD}qbw4OUD4DkA)MoH1^dSctqv?ZB>+2~Fr zt&d9**-(%>yGjd+QE!Gm$O9)UobE0s9b^L~XRhA$pcc6(Y5#GQQ8QDZdoe+~O>Z;9 z3)-xU!~8@EsCceHepO~4-2z@z_}fN5Z9`JSFE_%}Ap{!)bbB59g)KqkgwEsp2owcm zrcnAC6*+-B5$8vqsO>$!3_BN?$7Nq|tIVdIDaP#?aSxB`f!gWBwaJ-nUXeLZ)1r}{?m|9W)p_Zy5Q76X_wZq-PLvk!pTz&=uMswDwI zv6h0YEG7_d?74OUP^G=SU3!4C|H;>=C|?lK0Hz)V91nxU9UWbAhfhw2-_(qpv9~pA zeLb(j6Ng)25Qy3?zcPM==b`&m^bRVQ$s`F-o=a+0;472qLo>KFDKvE~BxGl_zr&Do38nb$fxR>xacGZs5Q7Rqo~`>vA* z9B9L`S(ctl03shaeeVDY@lrF{x5D`C z8Jz0S?;3qcv>Es1CW0#>b8v7-gEAI`_N+~1*KDYt)}NWu*PWTB&mIG2?(JOKZS}m5 z2WIuDKdMppTi(O!{g4-P#Y9Z$ZTnGIUb`l{oVsCC)sP);&B&y-gcKOCuB_}&SROaD zQunq%<(8=y2?7;v%eZxC*7VG(Sso(-wdgPeA&c(1Lj#U{_~gG9obP}lbNz#Z#kPfO z?|^3Wy#Hph0>e=w{|k-8EgFSLUpJRya%7XYAP&@P~Ho z_qN_CIQ2nm%%6G0wqU!`M|BIA0>I&~Q+dS1x__Q7vu0@(qXS-`aB02NiRw8N^>03d z7Lv*{EN4i4 z7h+rKPv`It(DDT8K7hO9AoPMU93dg5{DOk>ahAhx00b}ZAA=nr`6Y_=mHtxefP(W= zc0fHKWPmGKnqbV|1*N;BHo{h1B?_6Q9a9A%Rc68{)(ZmQVkW-aQ}7lrBmU$c;_Av$0Gdhp1Wrl` zKWv%F?fQm+fnv?!IN9<^QHvbVhOj zVuIgtneYPHI$7enrX`e__c0J0fKF&;yg;s}|k zo@*iOf%F$?znvqr;0O_LIwCD22Sjoinq~leVh*^;A52ljerY_4GFWJv9j*It3!D=2 zqMUHh?}`c4RBvPK;dV3o-utSy(0-G*!VX$b($9?r2?gEUwP4S%bYL$qTqrT;x#Up_ z9Y|lDid#3E2KIo?8S&Ld`#&ce))+t~rJdJvo!R^`F6mpxswaqr-+>#gm9gaA+z<_r zixfBw8;$o_16`;AbLL7Q;?RmeJQI03IcLqC_v59uNioU7+(R*1;{*_5QJ89+9V1 z)^`dnqKuF?-1j}89c2{r}@VssBIG^n}zHEjC z4^hq*mG&jm$G`t4AmytdiuJO7b~t7xG147W2%z<}$Dvjak$_a^yX8zdhr=`l-NVc` zU{gJDTw-Af4iHb5C1lmM`S=eS40df_mt!iJvnYd|r*5X7ZY#j+(gW@jh~2r}*3s_! zjNLOXu7APlkvdBVc%5*aViNpLoQv}-OlPFT#6^Wf-dROteS>K6)ITH{Q&43%EKe~I za0~dv$JKAEq;ngZZimkpbdS~4}d)RfZG zX@FziFg^ZDjIcZ^zdYYo5aA;UOqkyK!5ZW15#mr&fQOwMLLj3%*qBTl!JXH zzad&EfQh~JVn?zLe=8|tfd6OEypAaY_3cLh7W#iU7sncOj1MqQ#FyVfj?#T0h_!p+ z$OKRnpTka@Z#XM3bouFgav!O__l6&Q!DhVSCBfv|@XP&87)wgZQ2yeD9Qb)Chg<6= zq5kJB$R;MM4CNZxs!A1mZHPmq;45@WiIK4F@qAmYcHYv>u?uid-28t^a`PjS~W;=d(brUi+olsLa0{sq65_cy-GeF8$q;P`lo6m8>Seom>-WhsCjY5hXKeZ#Ko zB7E2B4CI(V9W16_hzi&az_fe?Xj{(U0xg=WkN8{ViWhP({QUe*FD9`5Sv|DYu>GY7 z(7{@BOEuqIYO>aW+_aR;x^nV|)A~>W4HA3jlfs!OBV+ z(p$*#K9#RAvIqJZC**wH3vy6s0Ie)sHht$X@6=Om{2!*#8#GIs;d|G=ogLo7GC0OX zNVru-l(Zc8KYHGeL@D}?<0~Z@A;(|?_>!`*{pWN9;P{tTI>g{|!&%{i!S{{FF;NjK zE%Pbvk>P6r)~^Y--Rv2>H{^c}U3~oe5QvuxHGc~x-}5-W5p78&9r-s*qaW~_-WLKQ zEL8M_I?4A1PfBmQa}5qywdbyTH|wBH|7Qum1qv-~?zNctyRrmS!0MQ0 zSR{=2<@(Jp`@K*&#;D4X@I%#ZWTby=5E%Ruqu#j5%dW9~zK>@wa)-f^cY3oTIO{Lh zd12sg`*s|DOIAs8=YyE)o_*NJo>_>v_a&Z-a)=x~#kukRmn04>Qc@qiWSg~(i512pU4~D#2U;OsRn`ynWKmVc;hcx%=khXfNqEU)ARzpczjf)9#CVg z&7B%Qop+JBOqK9HfgJyf|DO{RxJ7tk_@u{`*<|5UluK$Tq1p0dE9pjn{|4*^-f{+d zeVO1S(PPC~>49o{^AHJ{Ie7~d*oW%l-+lf}Jln)DX0?N+p>P=`DBqm4kt-BX#CPP~ zEv!*4!AAulgSZV3??d;S7m|jz=*rkU5IgVimWZo=mppfl#xBn{-CSQhi}U@0_7AY+ zZw~Jq>lfbbyYKJKB%rcr?$J*>r^W^Jb{PPs=zgew;qu?NFj0GZTtqMK^bPM6ESjn7 z{nHKr|5aT}PY0C%Ivgv=6&&n7ifKFhf{%S;*Ck%Pa;!C^Yl5q$P39TL)6f-QusYE^EhHw|Fg^s?z_QEZ0xO z5Z7|`@_>t3|HBPv6pEwq33fue$3bz@1D2QYq}f3S6-2Z5=De&W6DY-Nj$aMm-Fy+Z z@5Hd@u-c)9AX*v};_g?N+(rBtov*`PO%2Xa5~q;4rHv zqR~-gWbRvwm2|_=JSy&H?o9vWxCZnrWYM_Dg0!IC-pasT1QjLj8JD6p?`u3F?G4SoDo$Kv1}oloeTTzso1MK5xb0Th?}%Qyvot|8;kpmK!d+SAt#4`s)O; zLjPWu&P0y31fS+`LRTk3^sxutw^`qO@5fG&g@yL?i@zkU)@< zjY!rUM$Yo!dRh1qmHb&~#$X@8Gn%ySu5G+F*W0%Z3Bu$Voa*zTz|MvEWv8CyF2k0} z<)_OOswC)^)TA}8ldq+H-t8PkHpI13!(@>WRU%Yb+{MDMxAMQwagfm0zj4q2wUmf7 z5CuCI5rr1JqgcbUJwgzL7Ka}*&3jU4tt#7R7?ww>#}Wqqp^L3$?}_?(QdLa`qw0Cz z85J66txGryfzOS=0@AwfPy^*?W)T&2z?b8{EXZ$Of~Y_A@7GlHg10j|u<5iZ{f?DPPk;Tj zan{O#A0`W|6uu@U6)BqRInXPu0^ zcfF_x?z(zwwuK=Uv zoxEsreg6QKu(f6Jm=hk;JTmgdyW%l0!egZ%M9r5)^yOCMim>LnO+Yv~;xSFI5Lp7V zi+>F+&}%zZ|5V<2`cElH7~VgnAb&L?9t`#wRJ|UUthCHg$;{8u0M>HjR|mz)A@B))+|);Xk4bVuBdr*e;{-Sk3)#$NzjFf5I~ zc-iz|C7O_ON`4?i;g3x(-Uz$MRgSRRmYS<;P3MPv)d=DA04pRIy_X9ZXC9b&y>bw* z*({deU`|lsjz`norw!?x?4|hAqhitpJH_EJgtrQrm=j%J$I* z9ABL6G=>}ksKvCwQ_4AO+v}{iLL=b=8IO6gQ4(qXb{$*{l-DSbyS^w4)(h%mgGuC3o_=mRsKr`&m1mTwsogLYKd*YJcd7R0{9LTTo zLh(ZurAx2u81C2=Jm!lGPv_Uj`vdt#hHV0`vUa0fYvggrBo{G{!Un%f*#uDuDT|2# zpT&(07D{1TP5WvgjW~t4dIxkXjd7j!A>s95_mr5eTGaxJ2m$*I=(__^{j0xqmHkEt zkEY>LzqP?&Rk(e^!F2VuTo@>$l;gjn4SWR(dX*#{S?TF7W=t?UqYIRxdpLmIVqKMO z@SnZKN7g~)__}iQtqOf^$3j_gmN~E9R#t`>6R9%wKS&3R`1PJt zb^M5a6g_6n(+wrn_no>=WUFYfzCLSLTNkih`FSR^n?`GCk|m83XS(wBt&<`(^Jj2m z!DNiP4BlS!0vKZ~kJM5wNF#Xcv1XbL8JQU z!{J<@0aRk&EufZ9kyB2rXYR6|r*L>Q$9D>cKMw;{4P*hBrQ(Ht$ZgQFo}(wC4E{R+ z0Q|^Y-|}hg9~lbYjJGDW(1dYCpFdYn=sunqs@NMpJ(*7@gLl*qWprtYwL@7!d()Mb+Mx;iS6{1WmkvzUsqglFEu2Cfc}@#M(}m-7Lm8#oem zwj5LFGrbiFjzg7L&C=E$4$nPZpLRAYBx3QeUNZ3g{`PuV2Fs|NNy!N<}Y;1(f8^pQzvo98t9eR7CG37PDYXGnR49cU8AXm ztsl=U=GWLyyPFk~ahql_sXTlhJ^j$-uto)x)APyM+2VY!cuyLI7c1Y9nXcv)`JHUg&GHoWi0cwYm0t)P*YI+ zSV;5CEF}w)7TRqxaGncjEivk)%6q-@Vx&jwc6X(7hTeH=2oM2v0OO#-Ykrh`Ox6kiw=5n7*uqzYy*m z%2(g4fn!CKrOS!1sJdwA3L?tOqTo$Nn_c9U^9%)3))Sk)ie&aNh94OQQS*vHmxs~r z^@Acp4<5`My=XZa__A7DV!16`T9XIrS5^AQ81dV$qccB_R+J{(Dh489#M8L2;pF_{|?Lu zHIJtqLImuM3&3f@wS2+tx;2sq(oYMhg!B)FqV>%7fe+IYI8JlR5j671p!HsbUOvg? zWN}AFPd|M7;zihCsTr}S+wHk{#HOX6&n+dy$oFiyC!w>HWz+Q=&^1IxMEoFpq`tP_ ztHH#O@d~V_yT!xKHS?Bx9PirBy9G$An%mw)eQBKu-}HUB_&DYrc$lXG+JNv<9$}l} z-mMQW5SXq#p2~v~1RYGk&Qlp>3CuzYF5x1w@->d1>Lx5LGx08?uke2*{}PR!&}okp z{`04M!HRFNM#`qtcubsCyG&{AbYElYp$1%%+rNL9x_l9n60rl_qIQkQomwP@cdnKL zZ$hJr_MV<$z()`cC0o&8GUB{EBwvPao9Z}i0?Q;w1C)oB%YRpYer}dO1BgZ2xu&bB zDw~;UH>a}0)y&Srzh1{+)A;Co5?VKMZq3EYs<1u+rGNeUMreQIPVMofzR=!E&fD${GI0miIZ~~c z1~bmsjU)i;!6E0R*jeYj^2@4Hle?6BR!Ww2{4cuWWCVMkY)%}D0r#qc)s;d6)6ry5 zhgu}x$&yV82-XLSHD25_s24ikAQXO))c>`icdbXz#SCD+--O*Vs2q1La}ztaWs|_@ z$${!Maa@l04_EM03wT{q@h*>3h*%s4_(9?Ep~FKB^1-y{cb8YmywHX69>PV^@!7v9 zgA#8g&WB{1Q`H(+^Gn3LT4=J0Z!;IG_r{&h16mKxZ5|v;tbG8r;tc4QufgPd9j21@~SL)x>Wf{hE zUve(1K3IB2nkiU4KVQgkdo@@Mk?Jt&`bG>Ek8sa0319lJ4V$RC5+cDol*=uza6yd< zVt57Kv70>L{k3bM_wN$zIHp{{Wn1!ODV*F647(gdLeX} zCO2_r(2F!hpjAD3oWf|%69@V+88O+w2~v$9vbSqvxqj$)R9PM#u+{!Q>+w8(c$87~ z^+^_%Zq=8&A)!4stFKLL=r!O!bapG>gS`oCgAg+#<1@TNH_=8vkw;)%TJ7KGIIlE1 zJl}78z4hREQh`o7kiM(|#wtg*_8U4bMY0UFOH-vgI6~&U+RP!~d=SH9ai$JfwMe!p zOv|$pEf7I{wW!&Z#vvD0b(nVYE7o&AgymZ=UZl{?NPt1lqIoCtbf1Wsb+g9#a0{Z( zEe(6$9Y0^AGy_iWak-vgoh>fw*-4z>I3ErbuFY&S;ZSk*omQRf_tqnNuEd~qyNc!< z{bE0feRUPdE8#=FE9J$}gnjMNINwE`L1x$ca8Q>#v+#;66>Lh;ZSzVL9sMqD!EVUoc1I};_3S~@i7et7hQ9p=`kN*y$&?wVxkc`qo*Z0GL?j^RrRG(s$ zNcx?yQ)4aG$k%M~b1%2+$&+t_4#V8=;p|s3c)|xB)9!FeN&(x7K~r?tue%(Gdc6^& zCFlLBM46_@jO0&&t-+oKQPRT)_zV(5z@f>dCJah)ZJR{pi(115G;uhBg?83ZR7 z{8FL6PeMUT2G?<8dvtAt!AK0^5FitnM7*YD=ooEUqC+?_$?V#E+WG-K{m>LWjL{%W zMH#HAI;Nd2&&JeamWybwtM^+sOiakx$GyI7L@@Ol(}Q!aD^{|7_G|Op>o>2$+Zn67 z-3#$L;s-MC()md++{HgK?Si%t_2!fL0BzhkVW{q`o}t*3r2OWM2a7OKY&U~LGWx#SdGAy1p zyqylbr07GLKB*wb?hkDAY;Xd&nNwHmmmV`10mS0WXzMz2{E!o=d=WVXC?l-;{yh_S3VIwuQ&c51yzDGY=!feY{bbgjiqeqK6l~Q_=rDCbvz~e3#*+Bd`ja zp4Qzf)HED!Ugo5uTd8W9taJ05c3T$OoU8<%k3Zj{2wgTAeEyuHC$CnGhe$0Ec*}by zt(tee9Z}F93nh<`q=osd99TtoBjf4GSp}EZ!^e+B0hhu_N?$yc2BX$%Z!K3{lWd@n zeva;ijDqs{sr31-W9`uzpU~bjJ%y8P#{|LXG$6TJ)icl=`Vu>!TI*b@pI2FOlk?*f z5Tt=)?{c{9bBnBgQ#5`F4~|8p^dFPGdrSsJghn!@HfAL!i$=rMbJexLsftR+L}7qK zIGoy;Sdplfn!byMw}80yhn7u5Lc*qw{kda}xqTdu#bg>#)bK|eO%!pPE*Sgxs5!7b ziR&g4u>JYl)SGagD)I(5&;Up2z5`1o`iGf}^xSHsq_JCUw?&zXG;0V+?m_p>jOm@6 z2!*#)!NtLuc>xDDEz(ue`0;sZhVYj})PdrjUiU~;D;^w)HHeu`riIpBxuM7q`?BunWqyup=&j$+)rJchBWTo-z^2w9Cdz_EBO2Wh?I(vP>U=fMGetE2a zyTm!(KR!l;a|LNO&mn=2dl6LbCp6J#h#d@MM+4w5dUM=Fyju?tGY7^PeU5@`E6hE# zc$Ce=go&}!#cqFtC3#pg{hHBZ^1(P>9_YXK>}IAVZ|vahjQEsrMNcHX5MsZAT@mDE zr~OmGc2U^_@=#Xkt)3L7rIWI_-~ZXP&U||kQG7c>aCOy0R!*?s#}AgV zO&*766c{5i0x)A|2bI&?T3gGX87xrvys(@7e9EsebUgUr_{QBOpYCB&LE8Pe0c8U<9*Nl zyvP$g4+QJ*`EtM&`{li-5G+C>W!MySNKF60z~cP8N>*JE8E-$Rgr#HZ;<7#wsF?!4 z8PQh4n>EaSzLnQd~pB#C&;sIkNy7g;tb*U9FaH zo}Kvx?H%qiwa6mD50h~yyF1X?hw8ei z3ZFN4ur?oreGe=ea=o~aE?wzY;U<$@@9~4LuIxIl_Lp4jw6GpbhrPHrmg9w9_jr8F zXx9%IAWu-{j6K#acc@nh6w#T}AwHhIWjawDLYHklCGEVD-M}{Vhml$NwP49eIeGaC zxHrM*WmI@f59RG6n1{}_Kblp0P5I1F{PX!mgWW$^SCFgPgVwr?tL~%+&jAMd`q7Ac z3LbHnTKCVju(XO};lYaR0q%>d+8hR)?1A2310ih9n$j(zSV~vRtSj z*=inlR%+_N&Qh+=pCiW1`@gaCJ`3Y~Qe5IR(ffII^5xY&rgxA`WW=c^`K(=xajGiakdwZw_wmN zG*!nqzhDHgPVN0g^iqp4ovIBCVVP|tm-$OEx>h`7X16f< z@x#g6a4J{RpG&nD({@3%1|w8%yWQoLp0#XKLQu;iM*ZD8Jr z;^Vl^y*xcVGoAZVdA7fUKa{1ba6C08*Nfwv(`K-Q3dhZRwOH9&%T~R80Xl|JaH543 z6s|xNDu-aApR3Xn368E3jmB1MCkm>BTMh6gb6cq@%9RZC6vP02(Nl#O@>RAIPycIx zKj_I!&sK>Wt}6Z5h&-``dXBZanyy3&Oa5`qH$>p)edB$k3OA#i0v0hWI!a*SR;7>* z#|r95`S;Wn^?Caka;mIGEzi21^SA$*^MOg#a#n%YuM55AH+66 zz@8>FOF~M%3AsKuvp)-Km{N!u6QuknDdhhD2Pp*o zx2(|rLJEEA``?g4odeB#uw-NoGKIapvbLFCJtC~~1D5O6GeVFkkX?lr2}X#yj2Nc> zEeQnupAbce<-<2h8{GzcULg+jV@$!)C8u^Rjh0?F% z4$K?!2B)X+*p(=d*MSe_L3{0q``*raxK#s0GQsQu*l=V3*j=i8W?B~T3*pVJ zGJypcJ^3Z=Kwp(#PTcxD%%Zk5ds>$w^C#I`v|+7EYvV-s%S+;rJ82Y#ha>PoEHu;dI#&tw%jcSaa*^kjs$!6Qdq=fPs_o zUUDNW?#*V@TSHpcfe)C5xjFP-==PEzC`5X4<1Be^fg(M`Vp=}ncED!~Dqzi4>l{?v zO$}|R+1eKCT!7Xv$)9;`q7qhiS)l+b%D(iS@V zHEx|cRT?C8t>IQjxanIsFMt2`T1y`E_?gH$j3V;#0hz8Y| z(R07#9@)+>VN$fOhJ;48{|zjH$A8{GqGze(Q4a+Sfl!}h0IcLN+0k#@#M>XPwxJNU zYWoifSC<#tg<&A!tN%)OxPIkrzcrb@>|3AAojYRMgq3=a8loN11fS0a(wUJFy$^jr zxVx&uW4t5!;o@VQFSfh7nR>wx0IBc6p@o67g@HwSWfa&i?(2C#P`0)|lmm{!0Py&$ znNy=L4zN?f$vZorJ3Vojxp1gGN`lM=GFTDdAFvfA<1-e`7e-tRVPSr{zszm?_YSc7 zlC83(`NePZ9zaJZASH}J5I5+!WZ*;#Jdz+A3HFyvLUwIz9TIr{=)c3r0Fk`D*P4h@ zMPGDe7xKjDNN1IKc|P_3qV282s%pQr(S-^qDT;!GAW|wJf`D{bfCxxQ3rI*el9NzE zKw3nUmQ*^VQBvvdmQLyB8xw!;uJi3{pX=J^tiRrO$y#g9XFksu_qa#gHl#h)XE2}R zcyC!+?j!H5(bO-ScnU5sDgUB8{<}m!obcm3znj(fF^hWr+!EC8EtnutRBz{tBhO#;uN{qy&zW+xXeDqwMCPDzhq)d?bjZyE|6S_EQig9n_%6 zK(6o)B>Arsd`?XKWq9jX%lK6Y*6bs&|IJJXNq*;xc=;HJhXovO=xhv+jI6o0`HT1e z$xiv*(tw4Cff=H5l?RSJug_TG}fz6L`z5{@Ktjz{Zebp)L`kHiVx3p^9Bp z4i)#MCdNzt&Yjoh3GOHO>xaTSebhwQ*x1D-^q7^0$29V!WWJGNSa|qXm^k@4G3ol% zs~tlOV-%3B$Hu{~r%;v9)O6;;c8edU2BEDmeqcFXdnr9JG-N=%l1S>a3<}f>WBy}t z#$Wx*|L&nZM-dDZ3gEzpYV>n|KEDM~)UoCY{ZE}ga(3KXo}hAd6XLbW#l?xGTlDDD z3QB~@u@FH5%LN}}5Ipek@Dv~VM_z`MoScq5w=g6W0y4NtL_o}HRmH(cjglDAKUT16SLs_g1qClw@BJAa)SOAFNYg4S&)P?X9*gz%#)pGwi!?0xk-Hv%9-Q9lXmnvUebuEpsYv~uIbjC*(rj$?$64Yf>mccE2 zupZi9V44m&QNy1h3?WD&$X1|PKg)t^kECJOo;p78+?(fd^@J_6s%y21tUlAf039!e zf`A(v%OhF<($=P$ytM95)wIVeU*i)NY3)s+kqNR{I9e{}(Cxiz}yL+9%XoML>HRlezh_bKr1Y zO--sqC?g&|euo>@Nt>}Buj%rG6Ja(bZ}Pm%Cq6}`bVbl%3CH8Tj%=@J8&vq7XZi(| zM%S+WEOLcMZ8TC|X4IJy2bm|3o?}x+U|Rsgo@T#1oc84N8_0!+FWXHaEisv(#*m6? znF%?613izh`E`%Ah_|yQ>M62t-(nZj_z*s!T-@y+^FI`Fnp?P@@4i1Vt zR*3*vqKy5)+oVHdj%uD^u!o1os);T<(cL=XLzoxe|6;h*p}7Mm!)B7#7|IS!OuoP) zPjpzDN)`3Qc@q%O1;ftq1~ZWxP478Cj7nu~x&;8wG@yOukl26+hQ9V?nsX0=t(iq)2HY)ylcNe2BXWI3auT`p`_EhL*d*EFW><5 z7g;BxX&&fKVy2@LgCY!H7^0b}z6b!%y2%orz+}6t`kiJNN!VFzYYbvu5+QviO0dN1 zFtL-&Q455ctq)w00n@@4aD-l82EAW_V<`59zT)biI}a9E#aADbR9wDur5x_h;T`d% zTh%)yP^gqYJZACxQUhE^&9j>+fS7lug^wIGCVCg`BAbeYYbQ@_8e8&G0r9lMR-qKL zufD_EzqsjKhl2?_R_A5z7@wHPhw-%eMqQH7{~ln)7Q<^9&uu>Z1w{5BKX>icttYTr zOcI=LUc!g$wvparCE-W(Rp|J2?T8Ef+z2 zutIhZ3&Z@EtxTF69B}>d>WtCgw`}X8E+a*-Hya>5c!Qf;2_*UQAQP7Y8#z@uQxgPL z1E7C0+8EF&X#|*Smp?URAg&S#!nX7hFoRHeq}*lWcc{!9#2nqAzrgjPogPYBu0qN& z9BI(q0$D*go~^BM;(!;~T{gOu`7B07+z-46DU*Os<=aDb3!OF>+Tg)0n{*VIs=2JU zJ%A$~EhZ+$WjW@CE{)fn<{o~OWc!EE8pw8E~E53UEhFB;!-^k->%>gejiXv;Ww=Rz+#BjbJi zi!7~~6^Cgcz-T&Bt^xmR;krXB0T~e`8yh4~(`oUxbc~S3(QZ!#WR}7WzCaiB4NgvZ z*mYfCjUkiwoGl3#WA^-^grJiK(}$Wm8o4390ChvK62H=*yy995cKaJTizg zrKFUUJX~R;e(t`r^Yh$+A&rYDL;&xIVumLnp?X;Zkg~c!LzC(6PXK4C1pOe`?u&l+ z$}jp+si^$ANv;6XSdW2lAet?Cp067I;lp*$@3h`cjKb#TI}T1IB`jeEMBW69p#XHR zBZ_Iimy9r#6P9S@a1cG1vwH2D_1d?7Hs}PmJ-4!#VD|QpI4obdpYWXPIyB84!=6gC znsStsgd`4>Ji$;hv$6)f*kQJVYjJgTO@nE44;BE#9Fsw8;HQ<+yM~~}!AO)CA@0%i0e{ikOHpZ+g3%>SD|ImVy=b~WCd#}Nxgy#f@IKM6QI2~hwh zBC^T3@g$@l>|NluiGkP81LFxJ&84CD6bht+=pk(FN|k>A^Enk@$~AbdK@FIu@Cv}RUt<S1jYV)#mL{3bB42fXBkix-nXqc9yDJ!+Le zpy|Tdv!F{dhdMn+u!eeY^z?p_!jmF(qK3@cF`Be`0SE~J7{Hj&meL}$_vm1E!`fcr z+apk1y~w7XfqGaXO$q8>+7K^z>%I_BQ95K{O5JB$;b071Pm9sBJ?) z9w+QcH~^^Q%*hVqO5d$bH?Y^g;~WwITL(U>9YSWU>0&79eGITt0W7Ct@?&bjtwA*4 zXuiFqW?uGrz^p_Xh4-E~gj`m?A3`+wTg|g-tW}&?5Df?RmP%x}qog4Q052r}RW-l! zo29PH;m-69vU#xHA=3pm9UR{*Jf<#6^U^VO9+TdiDqoe#Kyo!C5lr0ABCQ93m&54^ zFAF?{{R~`3WB$l|PbLbPNMZF$mWrk70%>^Nrm<^?Me+B8k9-fw6u(s)dr5xVv?d+ri_SgjvnW=IE zQ)zvCk}iXdfZEw|`q_7CW^yP-%yR%|rC8zW^4i;5Dumm#KT!w<*vrUGVsrXoj&{kaB#@UT-ml4a&UM`*UgXmffA@Oh#1rbESw zAy%)4;nPwPCo0%JC0P`GWXFN}!g82>08>KdJ_Jst$R&_cP+a$}iX%pZwnadlH=O?9 zU>S)>RDaNK?OHycQL;)sKhu^VDI=o{U%U2&AHoOM{XY*rCnrx26au*7bJ<;2taylp zdC83$$okKKgVL0~en7(qznXD@Vi&kBM+qRDiXgkKEdeE}=bsl2Z1fvvzJS>#{UD+a zWkw0Ch+4r66r_gIzxMK)LvJNVuK{)7pra)bk_SLvV1iw%Tpj#pF+l8fn1yr$3Wh>Z zhx9PyzCU>!fDr}QR1F>W@O>ePpy@l8+alBfr;{Rb*qmYYMR z4!OXN=9!NO?dVIh57vN#~uaipqtFhiEzrh>}__z=+f1^hC92Tt};~yUgEqDdmJjhyWyv4C3Y7J zN9ZNMu_@Ttado>}BgAHZPJY!)pU+*d^Zh`pUu1`Pe{wS%6t$cVOg@QDSbe z$U3@wpwPUfD+xvViRflQxj<0JgizTQsxTLa%Mdl;!|enVQlcu<{jfG`DJ~#XJqbv} zcmWW%RLERUCc6aq#*(QC2R)D#xW;EW{t7k}C|yF$y2mpBsge>Ba!_ex19v;g|E|q2 z46Jh7`>m_$$&WwTG_V%dn-xwE@E8bxOSy~*lR@!APy<}b#;_Bz`~o)E2LXp1s3+5O z{!=5c2vWi22US&7U64*!KLQ|mO@pr=X%XBq1PcRl!oUGk;e9axYsqIl)u`B0VkKJ9 zka;QS>h#P(`)4WJ@#-`A<|CR=rql|O&~QMtBzBzLh7lI6$f?_ z?FoTGX;8Q;vs|_njS6tvLM1wCO6lPk9fh(<2O6`RW&dOg{3&ir z9v&V_N=w^D`?-Ftba1GE6Q?#&7ufURHe&poiz452fEybJ#|X|*`)BK=(9qD0UfsY{ zDC-6wUa45;2NL4`mpD7Kbx|Mt2<|%*uzhEtxfV!1-3K7sog^KV=d@)87z|(${~i&I zkUFu@b}WSRXpIhEa-Z;8BWLr~W$@bJZHCYlljH2-(pBnUjZB9h$ji%LICpM*|H-%` z%hN0vSYz4_qzLpH4B5vtPWjP%w@R9V+a1`2W7h>@5Y8>hE!tcZ7pSSf!oxtJe1r8b zx57gE@&jls+5qAnHQsD3yns|LzD0RP^>SFPubO45!uu zM}vF;g24#FK$I{+0RmXFJIRw3P_1m#m-D$fMmQ5t@r%ucz8)&h>(?bAsEMF2Tls+x z9c6o1J(%GrNs!^-})}jY)N3qasNGFp9V{x##I1-nY zwK7lPd0Z}Gwz^V9%%40z;xPS*4OFSofbz1_0!P`=;a-1>C*@tFN|3b^fbUe0YXjFSl;VYtg8#?qFT3T9$7~-fTAOd;vBm`*oJmVg= zd-v`Y0r}0$b9{@5Qs0N4-%8c4@&ZQgYHva>c&}@~fT32W&9D=`a7g1ECX5obuCWk1 z{v5MlzICe|uE4{`-yi&74~!rJf)vXCj{hXrpyfIj7t%ST7wd6`kWdjKAIK{J|9Tv^ z82B! zZ{v~k?lE)rAJ`G>00+;US0AWF0YSbhl9?{8U7$SpCdX8H^Abk(@VMY0J3EG#dC%9A zf%^~wSq2Hq3BJ0a3_3efl1UfIJpxQ&Q+L0CBE$WFfaD4ai57=eWr7G|0HD%c!e2 zTeqvk-dqvnzQKWo&;wwVgt_}r<9xdln5mkYn#nnX+&(UYW~$rf!|Gsxix&$9)&Z z6V2S2Wt$%$AP z=e)D3J|DrN5)biq3K&`xszRoJ_0(hK{@>7tb`KaSXyc1UahF`e{aJ8fzq%0p!~GqU ztN}EFI>P^+@0?%Y1SSGC`1XU)`vv3&+$P{$QNVIi8QGyBAvR#J=NltARm+`=;2zY6 zW8m_x17QT~)?Vtc))^~$vZl5c_*_V;vuPC9LkNa=)3^rFL%Y>h^AR5v(R7Pxe_n84 zV4(FN3)qe3FInmQEnYr zJ^U^v?rem&55V8Fe<~4?Zn>SGsgb_GXukl&3@FzNw41dGT!>!w)AbrME#84a=)){} z3&ZXDDN>2RqEYA}Ic{Y+{0?RycS@@6v3_eVVp5YJbkzI0(>;izTU#rw8xxeMk(Y+#sja{PII(k~DCDOk62V~L-U@95RsiMZflRas(G~Y``rY(&lhVKpspOtEG7p7aLm}1_Ob-O_1nX_j|oS zpOh~}b_aBD9;MSf4bW-TDV+^>q-ZW)^n#EMxx&IiP`~!93`ElB%Ugm&xhS6`(K>{- zfaHpLx$~VKd(eq05`tU`6;O3N;Bc^LXlS;#w?`qgs)8uTRu3kN!K_(-n1(q4RI)fQ zfEW?`9e#{6Y)2noU-y_8t&D2Tg>@LgM*<)4e#CeZQ$J~yXV`Wkf(_AV4+nNyI)if) zLTitT3SrnG{tyv~epN~U4^%Wqc=~kf!Kd@eAch-!@bK>4ng?dH7<3>7-vt{+&HUf; zVBsq5^-XUu`3Cy?H*OuE$c6w(=EcOt?o9zMF{G6Calhx)dEfc_KMm!JnNDfztLB;=0# zZX0)x{{9*I&1s7bfIN)k^aOJc8a}uwnq_=w&w;UXYS=VfqU3o-bd;7t8lZgP z^R9ud`@H806d|wcKJU46$hpzUNo?q(|Hx;1z2{r9Oic5J%A-h+Qr@?;r+F>MPXYSZ z`ppZ*(;Z}ih>3d`dcg3y14PVd1b%j9*lBqzJ+u4-NUh2o_MG0F?;!>*w-S{jh7kUh zw|CXhkjDBtq#MQ|hV!_e(5CSY0fTo6N2ua!j_o|s9VGb~R9A2SGs!}BH_vQ{0^;C% zV7SV2H{sfMSD2K+onP;=8a5R+?qFGYO+M(=t5YCdb{0sOdq6>xP*VD#)OEcj=+}VyR^SBG=ODFUn_CSBgWp8Y`rFS^5#vDfkPABIiFR;4uRmK|TRS&S zbMqMn)$M`xpZ^6jPW*FXkLC@#zZ{SH#JCj#z=-f+L5mP88)jx!*Ui-x7>F$A8g zeTYWuJ#3CqpvCLJ(t^Gc4U1Mel_xH7_W?5&2F&4kbm*&LD=mv^-f5`2aSsTPcHh80 zb?Vvg`rzJgmI+I^2r)+o(4n|ro@SqzZi#sS9u=nBdO}Uq?~+pC>(}uxt$-deZlR&) zA^3$+M{7ewAiMdQIObS(Q{PF)VKXV&H~pGSZSv}CRHyP*OUJ513p#x%k}%W}z2#B8 zV<}YFiAryO{xg;-T=#e((D~cXC*L2B%ZHDs+&SK{r!b&USzvQoq1htxD%~xbf4wPt zJ^US!`>#uE)_3ntRe^eij}Ha|3e4!6kzhR4_)gKq-|{^E4Un%BPj4Fq`1_9N^)Fv2 z!VzmVx)RaAN{vS%7g_oB8vGgrc?cGfon3vqp~ZYR>$jpJcn6D{oBJJ6z^@>nAeqA@ z1zh*Zt%|7NuUZZB-x2xwq&#|deB~*!aVMV0KcQ_ix)LlI92A5Jm&DFjHJxouAqZ}m z1QqfuWoFvv2I36Cu{t$x{^KWCR#rfog97HYoC-j2hzbfUvhwq7U~5Wq1T1J{ZF5Hqji$q#C>3n`mRK6SFoJ%z0aSX26G#kzpIAYi%u$KuM6JqZk|eVO5ol9>ry0 zSD((#=ZT2|Aae36#nHs{f`z4}r(Z5|V^Hw=kGAhW7qZWhsNhQ&sfIk0#6EMzUbcs) z)fx6E=pfvh^OL7NeZs_}Jj;f;h@2ACr+97WNFT=tLCj<(ayI%hktB@M+TEoCUkYz| z-KFC&E(HNk2;(Y0l^lJ@AEp`}&X>*g%Iuq91WgDKK7IwGRU~Y079r@4Nrxn|5V~a` zU&~y;t)NC3_J(uQ;YFx!u*~!| zc*mcW6_AMpOaR%A6s-y&SYCffuPMNpeA?fre&h8}Nxx3&fPz=`yPzPqdwv(=AXoVk zsGzY0b&C@pKYVxtu6+!`adL?NQ+^(#bs1UgHp;QYRe`Zp ztY#YINEPJgcj7t|FnYA!6C-O5Ah<00zlrs zg5Oo)D-{D7c?yt6yH-$L{sp8cVchC{pvha)l{3Z8J0Tbg&>4f4T?{0noB;-d^buqJ z!E!}UP-G;Q`LgQDSNV#{>dTm8ftmRP)qD!Z{D{a%_b8ssChr+^`zL6o6Z{I#nQK?ey;CsR|S^bab4DB7{l+NHFy-jdE#h42zhUn2Of|1MCxQ zTrvoTEH>s8ogrmo*qe2ww5$xq4w?%%tkRFXgh)sX3Km8e7X#o?T)BERq4k?}t*@V7 z!-hjkw4h;IJT^el72u(-2nlKOAa9k@YoOdg7G_dFr27nT!pS;b}CZR zSAeo6_lCilKy1kCu$tIrI^tJBh)Ee&kzUtZeUeJhm9|Shr2qC6Qe8qP31^(}*rJ}s zz~D|S%zLgE=YCXGaX_{fv`|k$ra=s9ylp3;<;@3z_z9@+F>Fsf15%x5si_0tg zf&^U=AB6>ueB=eBmM)ae7HhY)wLQ&xhSlEQ4(TRa0;qc!-L)p+I-7G@e0;$`RghhF zL>)hJ%Q(oP0;j1dpRvmp=HJ8FiHDk=9j~DxYh1{d-__UGcc17^mB)omt(8GzP7tS% zbP}VkPT+JiCT`NtD9R^A;F%SIig#n&ASn|IM-t-(*iRdZonXK5Ll3AA*lD;O@7<^kkb2@nJ;3-ntpcbZZscq%MUlLKNt1i-U`MvgKSg*gu&Tvz|0X zd<0{;3}q7h}tpsJR&98mA<-H;PG&HuZlacRgI} z?5h(r&wHSxhMpeu*;u#hPEt}*Vs7rnIX+1xr68mYy=2$>!f@wDKX@AUZ#6(V(qSqg zD#?D)!3aXQ+OSc33rs1?4|k0*m2F7E1KUY3#NOzR1^UsuqM~9pB99EbT6t`&BG zQ-ck@>B^Pwqn67%Js6+($Se2A4nfx5?F#wJPHUC zu{q+uQ;fm1w-b~YjI^%a%a^{ZaCs#MRrfi8SzjKZ0o*PMKTSeKiWUx88B7w0#K!w$N5{+!F23)b4r{pb^^js=7;!%F zQ0w8h&)iw_f9g}Jg&sg8{%5A`>OQ3JcDx{~a|s?)^A=i*6a=sVfJ?tB`bQh!GSR#_*k-a6{){ydPsoXCD3cFuZf;mSQu^V|U<*(#2af z0O-b8>~C8Rd2YQCS4Y&TfC+mABw;*63Ev#n7%6znK=B+>pc%?XSRzxZFEUP*a&gcX zK#&UxAmJ}(KS#8FRpa61Cv%o@gfIfK&ad9TCwGRjr4+To3<(ZAkBigIF?Ur}@zNX} z66_Wov4s{Mr+1CVAenSRhi@eo@7&=_u$)N3_?Ps%AwDUI8YQkInwrCmUEhx*fRw=< z)DButJyJ+hI0G#cK%!uwSoycq0v7BNn*QHUA8MRrIcqwW_cJ@_TPK-@(9N4?%bd1q z!DN$=k$J-^0Z=ho+1&KHpCIb&EU+|K!xO)3G_SU$0lymK6%>F*bt!$=vEAQjLE}S166-Zj6RvV(< zS9lk$k3tO+%eu=}4uxP`QU2xjSawN)?s!9UbGOcEY0p{*STcy9JR!CB3{WK~#}|3s zAjLa{i<=;hcoSJza*Uju{rFLR#p*8qV5u#&{nEalagT13l0DJ#9KhzJ%(fO>c%b^F zEHKE?=ij1$sjkaUVt80xakQeS@D~>eTj>|POsjDoQl(KS$|xyagu6~~+K7F`=m~`8 z(gpV#2G}u)b;Q9TAN&C`)&IJ+#EM`F3Sf%wx9@#@7cO7EFg7##eDkTVkK_q<2&IAc zz&huhmKD&8+1OYP0xW*|S_CU)pp?r)P;|eRU6-5uyC+q??n|~We6afNN1yt}zoE1* ztz`;cR4lvO6T26vmfH4pkVHFv&Hv|s4(3myum3rs3olk4`~&oYJ5>RP-w79dp_Di5AoO(#b2d-a zFi(}9LD206mVtL(n^0s*e55ei~1LOTCyqb&c)NMDE#J(z#8B;k-e%kaH zXEAcfMy4izs%J+T3nyhpzklyHbLc4Zip%Vcsha)!K~2l-DLt_-?LzF5I+wp~d_7ab zy5^Tpc%A?KAmud{0a6HbImp;&%lTcVSTMkHRm43>wHb*Kbh-?D!}kEKoQGegR=Y}7 zeEdybzy)eJL^ikkxM<~h5Yhc|icuLKEr=CD3fDyoJ`a#JJZUhVW~2VolvDG5t?N;q z5w$OP1?`ne?X#!is&$x`=hJRq0!=B9e1d{zcm1X+R%)VW_vT*`S=XphOurWD=FUtp z(Y7*A%vxc@xUEe$&zFOO2MZ~l8uZBIKfPmpYUQ6FT8KAyVFQ z3%?p{-rZkts)E(Kyf^sr-5ftqD5d*W_>fNZ9E&0TepG&l2VU>{lyE`VQv!9fFAtoZ zPpB5Yc%5Qr3g}sT*5cRgW_-G;n?*b!-rl(T`*Y(9k<&j9V02Au+{6tFd;7Y`4w;s5 zn65A}7tZ_Y_3fXxUG3~H*1km|eHgDawWaBiy6$TOWsJ)AP!5fVJ)B6IX)mI5J#-My z7$ZFCk!{k4=YRP&QCvV&oOJr;w!_e_*aYj0a&_ql0B*8_J;M8;K~sjqc?|d8l6!V` zc8WFl`D!nIh#~1JxGF}$Q$rS-JT_oneq;3suM3CIETxMrIo~Ht7NWcNxb)D7_|+-z zi4A3^XLXyOc6M`8%Fl9Yu!El1)3RMAo4IWYF^{UYd}|();nJ?y@}r$;LPMIaGgrQk zkqa78LFN_TG?Qh^MH9VFqe)_N9=mhs2Qo~PPxNFuSWi!Q4!g86-y|ib5hA7bD=)5D z*B#zu*DOs`Ge^o*iV5Iz!66~4hB1+RG1bgz0W?aPqOj%n_gX57mz!fs+~6Se7VDLW z9?15Vmf5qHw+eAQ-z^W+d|inX@aMP0Ee!1yAuRY74wL%R8j;vq>|zfscyDnk?(CMP z@Y3q2vFk@(HyZewc|aB(VXN+6UmqbSTuB3>Qk+N%V)edwkX-81rvQob$+U@<@_EJs zXXd*PLo)r#9i9foL+n`M73+0nTYF@H&)(T*;?cC_H>L>+rdV(Asj9~+m{#S zk-n#fPC9QTXsv`9^&M-Lh%?p8)6`<#A!GsQL7uLyDBkuTVt3B9xr9SR{w8SD!Uicr zn8wE;;#mzhywrBFDe9iiVGln3;tlyi4lg$1Br_(0KE?}O$h{`NQoJahB(s)kwq%{8 zA*#Kh_}-5rgwwO1M)~S#nnMSEY(86>#HbRII>nC(WZxdd>GoVpEXcZZm~NJ(Hu52B zgzNlc>k%(CVXcQx4n5%=SraV+@0lxz1@%*!jD)H<-WAGdT6rgqs7yG zW$CgD+c<1j#l~*!GxQ#D^N+kNNG_<{@!OQC+ld!6kum+LY(Ct3SK9hc&F|q;+GEmC zyhZ?r6eu1N`wa@n-o1eX$$P+36j)?I0-V&Cv^5h{e4BfCd-Er`>>${XeDv+houXr$ zqQi{Vk5$}!2}*Ih-QU0TX+asryKO5VboiApPs`>WD13QJ(g9(dkkH-qJM#Y&69~N* zGG%0|)8ue;^dj1!>KV__9sg|!N;fxQ1gU*>%ke6{w+NX2FSrbw&`M_Uf7>Zeo!FQ3|Ik@rvKm`6;c z_pP;NXs9(tbDqyP>Z$(5aUNAxl3O$#C_dSd+$<8BC5!$W-fOYt?-nYHdmhd-WiV-< zAa}a)u3_leM=^X?N6MUydk+1*+zD?kBi5t8k>4zQ7hP=qG%B=AMg|5bM_AMdFsJeG zs(BhNR>cKzx=p>8*_|;?fQD7TyS*j$6=ok@_J~BZlSSsagK;**wb#0N5b0z- z4J$@@p(M2-Alp@?g5;dozNMwtaDsfAndUVRNZZ%dlkEr!UYqQd4}> zyQ?gHQY|L!rNf*TWr^1IERt+%*n1N*!Q_8WM~80LwVlSGR$1QkWRqQLf zu%oG5Yqm$%YyKX`)&e&X$vdl>$!DRybLV$Y%gnFd%TEbyktW(iBw#n<@$*Qy!yKQ!DZ`g}4=e{4*8r6UkP zvOVF}vap*{I&$yn%kz+CXgruRPU$j&@a{4mKQ^?>TL(~5^pjtGK!}Us!o#~;lJb}r z+!_4CpCS9UmGo(E-_Tl7y5LxcwWNoSN&`3MdE&SB%J4E0cECg2%xi*abd;=gAE26m79#>l z;VkoItdDoBA8##q#rN;c*5#i*of61yAh5i5BO;TId(*0De$NAF;|uc1(%l=<>grV8 z$bKHtrxKe^Gzoj*zCV?|1|GG$=ua2rn+~3a`s9 zL58NUrwpO_zRR_XRl+o?quZOCh~2&BsQ7{noru)<8RMgSlP{>rs``GOfmHte8nZbo z-7geELhohQ` zx4YJ=K4$8R(FN1O?*p-Y<<6bHnHLvwY0jT7xw0qD#FR|e!=$@~PeRg!{86cp!NTyE z?R$2myH+QSBvnFy*!1DGZcp=&Pe7T!epA$x-_I~pe~t#v)PXbN1rocvGA4r9O;I7e zWK)Wrc~K=_$@&(b5=d|C*3WD(V9s=IXnAp6!z%FZ5xKBoL-Vm~&gD6e&#R>FjvM%S zn&nw{DkyV|nc_AHu`thh5F_u#%M(^zUsEZ)8hPqYVmk9Z#%^U`|5iTqs7)4i&rUh1 zN|(F#4CBTH;qoSnx79e2;inrS8p0U9#JqVK$m^wce?EByCs+W*i-?@P(L#!T@|ZvorKcq z5{%falVZQ!eo*`=0jdv+E6UOwu`ppR(WS^{;~k7hN7f*&y;YIA4R!6yOnY{gJ-a&@ zS{3fCt+jwLQ@5_T-K()+UE$=c;|}jO!~P$qrG`rIUN02>*|J(JuurY?+Op+wAMNPyp~9^pp`n)ZQ>+A(+`X}U z&wkrAx6T?5$6tE<$=Pvgqw{A6Le4wG3C#5=rpM7g5Mmm@=Lz@j=F$zYJd*q4r;*h7 zS>Wr0PGVxxkFKt+nO;BZqHF#|1?=qSINzqHvoA^R#XVy|n}3w71iW{rngH*1&3AgOMHObZ~_rVlNl$NL`Ddu(#TT1$D+ zwyUsncK2}=OkW$x)X81L54fjR)V)S*CxbNh@F`<3r#EFHY$dwa26Ud=>jZa|;?jl- z?uilQYsvRUZ?oE$Ar3m{Ih!UGBOfhshF$8-HK2J*wSx~H>8g+r6m;dMklpTLg8;gXO^B%UmP_LVv=3%e_O0Xu=I^=@9|_F|7P(&{ogQ=DNL``GgaEt6gq6v4mEJ;g;b;K~ zSI7Hb^bmZ{Ik(_}JL1Wi_wHKnZ_d;3B1EK8ha?l&SW9j1L}uKzo@O@++P8zm48lMToDqxp^Q|9L)%geETDlF$Q5Wm4L+N|YGaQX8Evw|NVd>F_G~-_R#S)0J?jmb_Tty)M7@!_t~Q zWU7~pBIlr~0ckx5iCf#eH`o8&jaFol63=#sQMSkQ#Pi8$rUE8c@9X2N%p+0mU*0W? zS=ewE3tG2tpAxxjf1*`r59@J)m%!2~u3@ZuZM2{G5+|0ZELRD1z& zu=lrb#_t6U?gvl!1b|sc`Z5w79gbLTx00N~;TB2n4#s}R;UMHoYexZ%20oU$OrQ|= zY;^SSepj4Kxe$lWuDbe1l)zwm8&h3UHjEQJRQDk=(l=ds+-B<+1*!LGop;yD2F$V- zBS0=Y6LKjND`OR)SLVJp%{Dr1JKuI|RnKOzzi+0uDSME7?)$;fP3?W;d7jY)U*V%c z-FJ(A?=3GjY7v}1;YC0Rl(OHjVl5QimcwDUT6fZQ_z*V=@Mz^9ORuZ3z)EPG(2)W4727PQfY=8vxN z*%)d)`;^0UqasGtzL&}DD1e$M71xXp zKesJIvt1AFmu@;L?{DsT(b42|z&uow-9<8+rFIes@;#6{xl{CPqo=O!EYu%lo~GJ`fg64Up+rU?M^UPA&ygsjMx!o; zMAve^_spaBTtue*1#DT;91Z#J58l6@y?t!+R9&@yaMuP+eg|wLKF->F$XQn|yRgZ_ zuDdA(X`-M8ycRMOgl)mAYL2An@XEm%3~h-VX}hq-iM|75lw6R!V9!-Vf3Lj$Fp(wHX_ZVFG;UjQBYgI=DO(F_nhE#c)@+*c zb4;g4f;OvX8;9(7sA~D*WJrKPZ5CLaI$!w*Z>R!OzHt$pzF%?Z!trd>KuOVFVs6Cz z=HTFB81A5UbzAkBEnv8}Dygnz-w6A7x*NI_gy& z%{p_w4vEvL!P_=!7I5HB6F9AS=*Y`g?5ZpqRhW(&{gyrdag?dEI^polX4mP&t_k0KV>qjPM-(D7E z+a3CmIKscYdH258IbhwYw%XE1mzF@q!xcFXhatSl z{{Ge7nwGicB4uXM`d`1U+ib({SxoE-JI=Qm<+U2->A!_B7U(NwTjd~fPcr<**e_6r z?y-iG`qEK}Tk6rjzJ=i@{FoaCsn#gde?krH7t{gW^Tff@_w)PL7a)mGU966*8cw5y zqFW=}lp1xlgcT44T+O+lb?~pR>8^vXv0IFntx3lN(G|bMItlD|sS|3HK(>(aE>s*{ z))^k$Ns-IN0Zv-6vF+S(-55z^Oc&GE=BC|yKHGDUCI$nM_3-R@UI?{@UR=CW8s_mdPP7pn3R9A_=!Z&l5~Vi`i%f2x;PO(YwQg^ea2LsF8d;r@eYUR&QCfZV)AXs|K5b2S62ld z`-71dA$O>_38@(w?`_j1=RmA@fqQtJo-UPPB@<1rJKa3=bY+7sF)2ys_e@LM{Lygs z)4|E$+J>I?E2yX~l*(ND&v{h|QFX{)zJ1I_Cpl-oGOGRPK!TzI)`{?ey(iUE1G*xrKEIo^e_sZUR)HG2kiC8nEw13tBW;TCD6 zhWEwoPwQa2U&`}@pVx{9Lw`} zdP_HyJI*;_>;}lbyH;_fc=98(2MMoR9lP8oezjbwaJ{cwJGDX~Lg&6(g)Q4-jJFrV z^nG*Nyv4h2;dRv;(bktdBD;0?&>~9=WEXA4HyIl$)#*16DN89Zv8fmQ^S-BFVWz@k zng6!HS-+(5I;Y3xxeX=Z&SXNC7LN*#y6Y@0RmCiRfU*X&@Q1*K$^r}IDG$jT!4ba< z@sX9%>P>f@7Cb|Z>sKc{Gh^Zsev1q6<_*pl* zAoL%4L+K_)WPTDqYOUl-iQPG)k+T>#*;K}`<)#xrbGTyXI7I-*eKO~R9So(mbHSIs zjW7#;#77?L)f2t(SXk2QUEII;!rinQTgMpJ*0Vm@l*^|Rn5S7!wR$2aMSENK{)#)N zcGhsu^#8YHOK=L`I$?{H360^L*Oj%VKQi-QE)YamveH(pl5q(U5}1YlwHhoiCCf#k zjjveL72(9*jM5j|9Zgzd$+`QrJzTEG@mde>9A8|PV6ohmiXGEMl_n?5lfGQ`-?DqR zUoZNO$Wkeo3VRK0z7DFb6P+M?3rS~NVW^?n-@S%$n-B6TdJxv<^Ur!FrBy7MLvW74 z_)5M|i2B!Agi^foy_r#`YP68~;I4H-;D_$&d%IdhZ#XWAsY)NVocp=f;(-zIaDGL1 z`L%54O~G4Qly4VCiGsBHaUBNF@GfrSM4AeV>9_dG;6F^1-QyOWGQR4cA#P5I{W#D^ zDCnXqneTVgzji46ir!N6UdVH&ar+dyVUN|aOZ7%OY^NR+I_v#DLZV#`MbFY!SmJz0 zb1lx~62c0q`}Fegs@r`+_#MO2lhJ|t^O)JtyF&&l;~PIb+l}z4>E-k)8THvuJy1^J zBfPuYN)rd)NBY3E8y1fU=Mf_h{Oo4jL*sG5G-Brb=;9 z*mI&?$l|FWUSB|e)zskQ`g7w!!^rCl|Ki~esm^@O^49(mG0SI)<#remi}VvwQhRZ! zmtHO0m)r4{qR-M2JmK-cHbrw5|9Rr~%^xvEA%zrEZTAlo)Z{VD2^l z;BGR)`0(htWyZ_@J5;i?^)bB4&fy|U&tZC<3UdZ&`0GV-QX9*r(UN_5r6jHhqMa#d z{q^1F7ym!2Ca?b${^qwP6sffuu~Osw^@72M;CG*-HnwYHJ~I>*=I48Q{d-pbE24z2 zruO=C6-H9ZYI#Z(OEsXjUHh-2A@NP>G8~;!YWw?1dpkSNPoD%8(jG~9i~T)Ei4l_F zXPo$PdHIV}`AB2)=(jMkH64_8cuvn#Z^d<`V2NhgK*!MWiw~{>66R@*1ib@yDm!wE zKZ~~qtMMUjWDbgdL?tE9Mbis=Nrn@#E?50i_#k#Y{73hFcf1P%W8({Evldp@6n_e0 zAk3|*`9JKvby!zxx9^SJC@P3jHYFfRr${LZ3P`6QA_yYVIgO<>D1xArf`CCtHyDJ{ zp&&@;4{4-3&v&}k+WVaMefHVUd(Qiu^UvnG*0mOkU(9*WagTfCXT;jt{dLGIo2i>~ z|E?7wm$~!k|Hm5r-}N8=kFtc{&+9)d{Xfh)jr!F+el22kf2~vSfjxeEu);r`&ngXB zT39?)F?qtc{?FA{c8XCkd0SfA`1_AcjN$hKr@sC?@uwJgmWL5PgIKTI+VbTvKYZwd z66^JQxR}Ki_=M^Iyol&A%8wWsDs8r%Q-_;Kb9#Fv%>@O8W6xD~hezn5=tPP;i&sA5 zuQfKghe`Fp96MgVJiTr2+veskM<$0I^X?>-dePo2M8FUafs-)(XDpO%XJUjP5aCW}v1T3VOmwxggPV-=r$ zF|X9(|4Zb*JXA1wlag}l&Yc}77p+oHKFH5?`l!4tJNxPT3YOm~gKO`;r#|*rix4FK zn#P`rnaz7R)DEktut?XM{}`lPe&k#g*c$&_>{i7470k@RRoj6ZB7c;%rh7--AmEjO z-@{yM3OH0ZJibs}{MXL@NJT3qw(mj9-s8s`e&#|R8iM<-+Y8i~tp4j6b$R)uZ6YiG z2Y7{ZYO0CqdnYfN-q`#1U;R#A48Y{Z-+yf~=XZ5v{_|TO(15+E)ho0y!8 zQQmciK_)1SID6RReIs=>_t5nptL}N7+RA(&c*vo)V$KumQ&LjauV0THKC$`FkDvekPJI2g@7?+|8+AlKb_rZgjPl$V zr*0}89PE|;`O>A!9LhN=YGff)?9PA(e$v!XKw$Tkmhw;hoq2@J9(Gm^2-2E-|-RU{m8iQaNS?-QFf~9;l480 zzZ*P$1%DKuMi&TTtGb~tH8F8r8YOrrAIGQZ;?p99iIC=?&#c?14o}I%o+#O1yBi%BW>R85ur*~zGL-68SePqp zNBLakcC;2%f2!NU{t_KocRUqnu$ESb9>)irx{+LW1qHa;+ZC0CySlqe*FDsvZ?gFi zdi61(hKF(Ss_COp&yThn?=t;XuTnCx>#;ytft9KXgKCbhhO22=xea1h_1tmn?&tKA ztMcff3EqDrXG@2=l+5h!%lzbIKuLbjSq}P&HQEagva??<^CGOQlU z%!g0DJj;`saOche4z+~afOSYc`+jJd6u`!(pASx>oAqv1mLN(wFV{Mvne;vCaBES- zbh5(H(a{i<_4LOt`|jS)Uxm(QUR0TfOA^dUL{gGtPXuC!gPLEz1|?OPpHYpXc`WUu zAE)-}Y_WbJmBaML7sDOv(Oe<>=-jz;ABe3xXk2DjuIv{yx{ccT3kv8m|IpY-hmO4? z>Ar==Pth;t3YWQ;>}!0=#JijA+KG+)ZxoCA&A2$oxZZ+77!=K5IC0eHb;$tBct zZpm*mbFz`_Io-k=Qnoju0t0WOE&Cz}8;)Z?H=v8OwunH3Y263U7cZY5Vr7j%M}ZIk zv9Z#;oHmLY%92mFYwdtb2=&Gyr&9+p>7*w~U~6>$IlHpEnVf9*&a0wA&X4RR^;7j|iR)aRe|xtLgyph7%O znpRo3jgWg^fuy87>a4-=_ydod>?B3W+k5oqdm?gR2x6+7jHMgkhIaF-uKvW+Z@NM|?=@Tkm-lK2&>?@vOv5Aq9x1a@#fY2X}Dq`r^6SlUtN!FW%hsskx zs)U?8RS4$5Gx_9@A%!~IxybjLr@#y42xb@DX3 zWE-6;HC_d}oOz6?(TC55-b~UX@{B-+MBy|y`6(zFprQiT$PWuZ-U~jW6n(wCK$=mV zh*b!Fh^i^^M55$jg_l{MJ_=yD2<8d;}J~(p*}pt09%Qsy?pmuLH7qsW^8Kffw9D_{?< zkNrH2MywdDGVGtX&?Ms})2OlDD<;MWGL9@Ha1_7(H%<9UkeDhY1h7jw(xy;cx$5i>-ix-O3?heO)ZN)*6A_bhrkL8!VJxWVqcq^ zFQTL1N@K!qQVC)4wxT_*6uZSvviE$SdQ0T85x0q5uIJ_qz+?tnb9SIdiC)rOjJ(m& z&N5@P43SMGsD;1u$R4tw!C|m?IdOxIK1nY?oe2h~`;rwV&e63y(IRmRYedJR?~DC^ z)fm0dWQF@?v7z!&l=4&1F@!D@Ni-JS!krtVF9;tUmQ)EXL3TJE(a)y`lYn0E8Y8;s zS9yMHqB4XCU1`ncRZr6Og2Z}N*KIV)D06*jcKSB^G9{^G{T$-%+E$N&^x6nWDa~rB zZD5c5feYF|AjeCG-`$sHO`!(SP`Umj*rG?>=4|(Ia-P|?T8jSICC@iNHt!#>cW~H+ zv58u}BRFa{fVoibR_HwW3XxQ9R}d@6x8hOTrEIvpw>8^Z8hn5xDa)T^2h?3W$qhq8 zL$~wt^2p|aeWIdywPPz*tawkL%WjR-wm(2C-GEL9Iy4F1MK{4djQM!K?K?8Fx2{qHY8M{j+K{J z84Nj(qwWiipebxJ&(Ovr%dd@*-@1>Nx9(}~>IW5%iM{LJzrVX_4Z0sMqV?kaavG*3 zsDGL4TsAoF>${h2UC^!F7x+%RV^fu&Y*~4TwyI0-bEUyO%Aw=I66Tj5xsTOrRFxm( z_X#-NzwYFrvnRIX|6IdA9=S2v(A(?w`WvS0MUp`~K9~rt5*leNGfvdH+8ypT)joWD zzH+cfqG#|p0aBtZ7{IZ5_RWVf(4rI@RPV!67^R`;kuFkNMDo6QrV0w9v)V z;4eB9N@ASaK}#diPS5-M`zOBN*-B@HU~&JE0qB8*;~lSFz50v>M_Z`lc%)?F@ML43 zJb6O?zRPMnuVazhGONwr4966KN!?~H*aY zC%f*T0V7KNnRN1pa64!D`T0pOqas#GuLaMV!=yj(pmz}Dx_LselYz5wJ5n_e1>mt@?qsgDhMzaH2XnF z*u!__{HQ%Tm26tDHYzyRRMEcFP$k%r-!lI_DQ}2lNsnje#=i6hB~G(^P7af^SM#cg zxrV5EYSF>_kGS=;^DVHHFVC|*2R7&PtczOz@T(FOUFf=8Ih#?nVXxd0KMD* zDpv!PJ_Fy<4ke*|de$YhFEcYU9knbiEpsW}ob@18c06_BFK$^XVnKd&4lvu*=|O*R?um`Pmi528&bSCfJ0n2{Up!LlDGulKdVB1i1@={$AY z@3+>)fu@pI?Y8_dYT;npjS8+qTsDdY>TEmfaV-pDQ(v05&ATQmIbVy&b**mIVh_tM z>hu>?nG2#6W@ZYs;b7~F_Tg3lT+NkSn}~0^&V8}uPn}|q#jMd(~iGJ9j8|KwY?oHkgAmiK zziPQuDpywPF}EJkj??IQyCDKnIb2a_~D zKGK;3n&4_5IFR-tgKs`~k86H;m#;#d%4PNL_8K#E&XUgo!*S!Yi4Qq(qe(A8;MvlJ1h#$6V%Gm`?UU`Md2ocIgx_jWrLEPAq=vv_t6#HAxAv*Y^K+3Y zVpyjEUHiGd_;{Fb$x{MYZc~n(8_g)iUgYX$wL5c05KF|Y83 zn42s>twvNtIa0iW_q#e(_$w7b(c6)Dd7~ElZS@I9TleWv!S-J4Y554tX_)d#Sl<`NE#1~tswniqcuW`{tS0$8 zk*d*uMIlZ{-r9WkT=U%{65x@n7uWhpqL~_zi+rm_mvy z6bq)zirvhAx)nl>7``-*g$QCBe1%3rR-F(Stvp^M0}8oo-x-6s3y=7Xialq#1&elT z=sTIn$bhjG>j-4+I0bY${|NgQc1+mTU6}j8va}6=U9*VUBg^_IpTu0Qh-u4WR#uM} z!#}BE=KGC}jb#s}RzyS9DeLJmk^c*%X8v|;l&{!1{b!dAmM0B}2P)oBTXH}f%gD4s zY1=7LG|KsCXvlNT6?GAh33Ky9}ntM^r#RbY%ay$I!22WfYniVo7Kz&v**0Em5Y_BA~| z9CEJv)*RSX<{9%5C{on#BPiac7l)gPG}Bh>%q{vu@Zwm=OR`>m9UZ+N>N}a@5*^Zq zlW}z3B%m`uVs555iF_)$c3;#+G322PJnP+vQ4$j{4Bic>sHxH7Ot1{MsTQW8vHq3O zBnN|VTVsH`&VPG4A3emIoT172n7(8(Ov$A{%EJH@9u9Wd= z(K4{-n1CJgMEPBkeL>b&BOf>(@?sOU($;|d$;%XzONC(t0lV}aFUCIn8e!L{W!-9B zR`Qm(wWD;ENV(HO$UtU6n1KtKW9@Q39iMlCV(ozhi77=1{UyaE;OnV8wa zvEL||HQHt7hsS`$0fJZ@F!1?iZ)ax*vh87VcZv)M!XyX#M%h{q7VQo=@`>X##^C9G z-&0of3f6?nw@VKdDWu?m<5+^DBj8KqptYWb@3)DTD+!hY>kzi{f|-qTfR*xg)mGiU zIRM$n$J4*)8GDcUppHKDu=&-ioo|Q_)IMm8EQO&&3meNHTUv{a#Gt(!7QEslNk|Z6(gqO z4$K1uTY~3W?JnKxQo7gQtr_5}LD;K!w`sU?&(-GUFpn(^bP%^unD~OEpCtt8_Aza= zL{w{`?;Jq}n#)KNC%ZS=$B49M`a1U}6)nw$iaK0#9nk5}{`FW0M%HNp-n8$;B1@gzm$-Jy90@G9wWm@K?F)5*O^Nv+`4G1Hfv z{dA53)7uBJixP4@oAey?;88c1bZur96RH*)ucYTUzmPxM9X1NVD~2d|*Kly@gn)`Y z&x;8}p#qlLtq^iM3F=FB#xu=Cjk~(;GKNtda>qs}Bk#*J_kh39{(DelhPX#;7>|K%B4?%B26SR)wu{{mB!AM+Os_Eq%9mPv zqBa{4d?N(y?Wng2*tUg8KFz5dTgL}P9?SAVkEAg;t=?z2a}M6yygRl0+!C8z>(zM| zv)=4Sk~ec+5RG0>|$q= z`X1Mj8uvhen2MO@8{8YYpU$`&U4ezniwHT}&W*f|Bi~{6ic*SqoYU>>2%6)&+Gr88 zVSeW4<35v%#HT|cpEt};J}aa%e{pZfK~@G;VM{b%{?SqNvcZG?jJ6z2p5cOuI264e!?P-$t8N`{y6Sw|!sE_M zqQaRRzuzJqwaLOB_s6s~Z}KS{`VT9Ghmwx%1vRzcx#{637{-yo-Sb&Ri(HeCXVS)m z@8l9YZ5Ma0dTqg=Zz`OtGr0@ISVaTkKLcmMQ@lUX#X|^_-vHZ7lJuDH4TW)WhsjwJ zW>|3rlvpdmz3NdI7S*#~wgs~<;`;jyhuWel5(1_Jh<3h4m!T-vek(KhAzLD+LD8vy zcy2MV*sLcGTb7dvqR|Jna0YV~PD2x%iNN`%B)L3W#6TY*-h3Z+-viQ?`qI7*hrubF5`+>QKK4jnXT?qg8;rG^?A!-a z%1D=A73TOrxQ}7UytCqfzK%{9n%yFmpg<@3Unj?r!6>_-Z=k1$0CrD;;JC?Y1${6k zb$`WM1sxkwDb+0By>MnWLKnuZ8G<)lk>`hT&Y!`ojMvkx4+~maT5Kx`-ZC;EV+$!E zN0`J%EltF~gssRK!Bq$^h5Qrg!jOMbRT7S^-Z1%yG)ej~Od!IhIblYk2U_wuTw^6T zM5KGiccu1ea5bUY;gZx-6&8|-pwMR=ptfa94$s4^3Wa|`i0#_big-D@mKS|@6UXbf z!a<*yE?O>7ZuBah%n^Oo?z{WA3bZ;obgX{5aJnF{5LnMpcySNO@aGs)g{j8NXBTvI z^6I^cn4No{0ZdNri!1_jnC%U z`BS9D0uT6MJ!zuV%m3gShcQLVl2xH}HtW@S7U3J0S^ywc#Ren)tud?YI2m#N7wK@V zV|Fxrg;EH-&vLFYt1zP_psDriSNZ4^N}&ZJHnCAmS7?d2bHs5GhgZvo_@SXYG(`55 zy;=Y`%pYE2di#z_E-N3dOiygU3V7I1%!rH6^tHnrtDh+KwuRBh7wO#p5-?h`Pgw-lLtGn6+UsK3<`uiu%rBB;4bL)Th%^Bz7n46tK{n(>pS7Cz`c^MU1BuiCfo zqYrd?c`JOgVI@{m*&(pYoe!|i!Za_LZzoR$KvH)b|2tTa0I z4C#l}A&G)REoVC7O(P_qah$;Yzz=Q}wn0-{C-0fYfK63rzI#-oK;CTk-B3|e zdyB^WE@!QChHaIP=cOzPod&iP?0G9!EK%Q6ac?k4&TAftpW^VXlD?@eEo_`T0eube zWjIyHd5nH3juLlZ$wy;I>k~C~IqiHQx<|n!Q1LnB7ZHDb)3;QU*P%s*`bgYOe7#XI zHDIE5D`ULfr2%H9_m$@N^X9jf@fc=P_x)ZO%;5nvxRs5Q(DS99{&n`cek6yrQX_Y!2h>iIwTZNc{(@qEmdBOCOHLpMTMEyc>{h>3_ z_I>rG+G0_>U5Za+9(sFgW;ESukN(L%T!KnulECQF}`R4jcpkkR4bwukn zyFPLXUqzKrDX~2&PHsLN`-AFx!@ePM{_I{G3Wmf|9b+ZOb^XI%vh*c-+_8b1$}I50 zn;CEPDTOxiLj%SF_;n;FO(OE>bn5Rp58>IED|fTfi~RhL|I9D={(ty7e)HCvC`n0# zX_K;NC_bHl@daE>$~heAKG#>Odw0tnwzwIKb&BJ##ek_krM4J=S=p+yr>=qXEh0a^cDsnA>$&Q zT)MVykccN1s_ILW=lgf0jLZ$o1ck`UE;lGdeHwHyuA7XP@5=cQlYgehbL?$*4A)!cYSbw3_N7X zGY{rHaKw2^7cm-a=T=E#;d*7HxG1TQZ?2;DBm-QOho*HnM+{17r@-zg3BjRESx|RXcgNGR zMxvzz!+@ADYR#e*-VlN$)xt!N3^>UY7$qpICp~s%7`LJPSJeR`7b%?k0yUO5tZ(W{ zT6SbSVHDplqo`Q5$KcsfC;)_I&*oze3=osI(fX>&XkAQGhb(AtoE!W*_E*EuYuP-u4wrLJ@vGOi)&|@+y z{mgdt8?S)y&+r#tho1)|GuXHt$zAGPwPKu_E8ws=wRaq=F3tF|%QSICq_;(L!h=QT zVMGyKa8GM$sgtbCX9+Q^txueFsd@^2&YK@&h*MY5;|#{Zuj$asstg1>-AAfWz1JoyQypeh7kkeibR`WMI!C_0wT8@}3_%-gy1dWmp`vk}m6D%q6T!+s@a0Mwuco4s(Ajfnh;)xf zKh5V3sOso2fUq9`k2aP5IT?;ZkyZ%_(v;G`pAd2nzzhu8WPPH5@-jZso6HG55wP5Y zvN>P=!Q~&*Nbq;zUr6RF0mWWW>>ROyLhmnxK-exHL-VDJGl;&6(cLejfDuVM6Pb?Lr!mB4%3kgceaezbkJu-*EgE8Mq+(_g}+M zD`IWLnY{&*=12BeJ6)I%mXOeTpy@h)NO`s+OP<`3h;+AHV+igjV5eIg&S6>S)Non3 zvA=UZeP;Xe^b+j@^O(Mu(o5qB&Kt%RY)oa3JYi9EX4+wL*S>Bdy+cOkRhqwI(d2Tp z#Xu4ZK`fjzen%_+NDMPTzo)%~fv1&5T&9W2w{X?kn_P}funlD$$`-gRJMIo-mpYMi zNV3rae|Eow*Dy*}NSl4M2VCnfkEb>n%$$sf5_K42#Bg)Xfze9bQ+MU#9fEDPsu5Yc zp9gu<6j&TVk~PWZ8#MnH(P11hzoQX;g6*DQKuaRblt^F~j+)00BQcKXD`!czBx(sd zF&o7TK-dG1j%aNM^Vt`EQOQIfOB7$ytA5FvQ&xqu-6@!cW5;||x{TDw#h}g@L7Tp? z2}6m|uFk@J-`;ye@kUUl$#HE34RSJ-qPZny9Y+>=JMD%EZHr^y-v^Y8JBx_7Ryjhcp!w#CF7=b7nWFWaDRP*xTLGhtJDYbrnG(;nlnw4FL_vF>9 zR|g^t-0E`kac1Yu99twz8q*_@TbtybzQ=IQ6CC7Xq@A~;2n(y_{YAotC8~H4vA9m` zkN_+^sUDEgr6%KYqyj@jcZSqJmdKXf15Zj*1|>8{r}a8ew`_a`v4{N0MjaGv>*?tY zr-L=)K3iEfu-G1!^8LMAOulM>r+1tk5kEVtN~0*KXRWB9-4XmK>z)Y8d_Un^LPB4AAhr=qdTNfZC8 zMOqbQvS}c)a*%wKz;q@|jsXg-+tWh^+L7`@qbdw?=zoZeYQB4^@9xR!WYm_C22dzc z&K4P8)Ju|Qep*shoExY3@HFQ9f?mySnZ=~$`B5ZCN)Q}~w(2NEoncn6{@pjxs2UMK zNZS~Cw8Zp%o^9WDGGa<*(PFc$Ui{ve$iHw1wAnV0{%XWGGDU7j#A$uA{6U85NPPrJ zH$po0DxAR&o zU+Sn*s9-nI_vw~@GIcVD7=2wgQOp@LmJnwAsi4TR_68{;Q4kSNPt;j~1K~sF5`0Pf zj)3;sg|N5`X_3?w5zFM#Md#;>kyR+e2h(^7 ze6(X%um%NnCme;BP+};Ci7E^SFDxMLVerctcK$IAPR`CxtWm!3 z+Q=h#z>RQsz484L;yb=Lv2iSjl?2-`Oh| zYMqZwiAXQ~&teeCg%Goy*3Z+Z!DinPbp?Sbk}5v{{}z|eJU_UY@;&g5(P*!Mz?Jsc%GdTSKD?;4Fz!<&ye6uHH0OGwqVt{G z+&wTf%oKibwHwyL74;NeWNwg*UlXK5O0} zr6pCb7jKB&F>SF87QX>G zERoDykZJ=(>c{M%_b)uUh~xvxyb>|`0Z^^^_=7T){3$J$eH~g z-CW{7bb9~C*Zr$rM*PqG?*9XqK>R1J|G)YYh<_Mx_*Z}Hzkez5&xy&u>hV(%|C6xG z|A|#V{N?|u1QP#g@abQ>3jb96>tA>Y#J}*f|Cx`*{r_K;K;l1(J^nK{@xO5o#6OOB z{)@l$-@o*~&Y%Cg3h2MipZ{Z`{twU4|2lvET^Ibr1^O3nkN-M<{s{y4`?A$=VQ1Kj zWyzaGsSOV@6K=2BX!4P*y#3?0H-lTa@9jUfQ}$l;e(76LY{zJ6mCwtpzBJCou%?-z zS^DXtl^N&d8Xnj=6iK#}-b;FZ`gr-ktmDLroIKYdx9LLST;R0g{{1Xvl)oGJ%Tluc z1W@cBvp(|om`7JeA97?Q`SZW{QenN;imr|8Zzx76XMsH4~w5TapLChzh2F7zrUKs z%WG6=oiy2uEh96LogGHM^~4`?ojtZX$K1x;KG8}YIwbywZqiNeqx5_H=oDhhX{&w% zGzc0R+TC}E#G;B8J2Pu?f*JWURMzu1n_yOm?oYgljEsbT$=@SS1C0NE?axllzg|kv z{PVB<|KX*o&LUKQ-pLsAXx8wFWa8XCHt$I4*pGpnKiAi2${y|{4x5w6pdmmOH#@K%7SL)w#qgzC`Dd(jB zM%r>Gk)SfUK}o@V-upRnpT6zk|7%qOa){0yR5$Mygb{Z&*fyOGyw19>t}pZNMG@}~ z3{=^+Ghy78J|On#UM6O-Q#W#%{$A1mVdb4P+rAI?9c4aqE0=2h?eDgt|0bw{HRId- zs*u*ozd7hzYF>@_?x;2ZgtwM+R#T_np?rnKv|BEBu z$o0a-%wOC&Tz5FfFf$iFg)x1;N3QS++qYh^&@7^n z_SnLHoiV7IcJ@!CM@LhPyx>HokmD2fW2tdvlusP_u7~oE&sqBJ9``@fl7}f-$Ym3z zR5w*#z8$Y|Imh+0o>BNf^gZ=;;nyMW{XfUb`n@;&7M3w(+5;TXieZhB)E~IYLwEvsjXT~AYT2Yr#G9Pw z2UN&Mc(Sa3r8-2wXk6T{x(DJHo#J=CTCPP^>P1>Zg-)UFzhyq3O> z>E?NjT8*KN$sIQj`{_J2y34V~-(+!aOYgVoHJ`l`{8(Q%%7h908f`W`K00zyrEuVx zMzVXObxsN^%T)1+-%nTe8+u2SQ%=&N4c}7ob0)+D_1C3@;+3Y%Rj<@v-mH= zc!FI;tME9Deqmrm|AS1+sFKi>KGT&)^cRdXMVeGM{z_jso4mz|cD#o`FYlX+9}edS zS?k?Cc$!|sc6Fv@+qL71bqC6>PkiLfw`nd^Wo4E)dop0C0L1kVgvelbl}@&k%>no2 z#jOcic^fh0)rRe|ywMp2g{Wme2J6N?`=JFUYimKKC#DO(zG1kF*xAFXw~S}|E0RZn zX8d~AVRHDj=ExU8p*PEW)swWT6V(UK>-vw~qyN2~KDwPfyaO0?%X@3qu8qbH>FroP zR2j8Qi#YV1TVqRgfFw6E0;@(^zH87hGy6w9;x2u9>y9$Y zPe_qpy?Q3k;_;Up`UV9t@m?QE%-&A^U~H@APK`i^FcG`gu@E#f{AqFF{&EoyJp-YQ^;G5MQR{jS z@O0NxZz%oA&hjH?DuRk?pfRb;V@JEZVd?yerx&iUos^bNOeQ)Qg;(Cu-+dE=8StQ2 zP%SJj;QfXIVKfd6DF=G}i`9+a&y&Mj$N`o{L;1}>MXaPc`-E@Bs&)D1(_$4t?00g; zFPr8ywm;DPdCW=f2**xN_02feN2`0Yfa2eI@bpR47%=JY3fQ=LbLDZCAGhZXl+Y|GsuF#P(0@cY<`E;i}X*tLwWyJa7rJzQa(Kk`wlCi-CWSx)h>xjK7slbT4$(OJ{Jkb~3xL|a2= zKq==$!`Q>Xz->0-YpKq^tS4Ml22cMXp%pdEhkj?-&o&WnuGDSCz$2m-_~f&Z$Ld~& zGI?>?Xph!yQL!CNchx8!D-Am^QZW`wE)d6s_3|o#%pO~r-a%_&4Af3)Mxoxo&@svu z-rpA70|!u7B#>LW8$#&2)0k8Q19rh;8rEIx^-v%=0BYU*OJsUs(r&FCsMp9GTKz`z zh>+_Ho4H)$8_s)`tBR7+uQm+}++Dny+)OmuUu2&+Fx}xcU0`QoKivLBL=4a#48ir- zen{c_$GTORxxB`FxnX&JGu8P_3wrzE<~4qDchDwgH1>RuVDKwo^x{gepif$IqvhpY z{{HTc2hbvGseo*Vdppi^>If`Ahv9Zv%w8NBWqtUpq3?cB&>vd{1d-%FeUyGP()@=2 zu3!f?=m_1W?MsVIbM7!uLb~E^uK985&xdk>uUI$W9Sbp-xqIs8Lp9}03oX~B*p#)1MU+k&ws7q=in%WHV?3s+q@_j%Qxj4{hne8~by_*m&6LBy0yy$2- zGC(glB`zZ??t$Dm1+0~si=)x5#+<5go0n$X1WAGh-k5vI35b5CG}Bl~u1cMaF4=P* z3rZ~HPM%zena+M7o#6VtLJ7%XPAZUdztp{UI5&{~*kSwGvuBG(eppeEvC4KAUz>Kh zN-FVV%L`g9hVgE9vxxSZnypmV4@|8^uev|R-%`D44^~cY>YV61j=9$>O9Pf`Uv^zT zm^t;UUT~Z8PTtFvLb^GwYf#W$#u|0jfn%+_@o7axX`k-Iy64uL^0EIavO(^$6=90} zn7wrc)#{n}4yM$TB^#-3e*MZ2%G+tfDB?(ORm8sslp_1GU{wi5uRr&%_dN`~F{*w0 zPJk#Ho?C^cv6AEp4Pa>N*wl}#u95h*7CNgGg7v}hsAh+x;oFL%k1Oc~hr}_fbag{K zVN{?hK}DonbIASGZ+hqNzBs?HRpjip)%kPf+XmYUSj(m_yxMtd zlA%#M;E%2DcI$DFwuzt??c7mALjrZsD^dS%k>+G2BO{CR;z4Mr=h`g{IC|6sth)s( zAAAMI_9nG*M4EV0p>pRvp&K(-GBa&-9C8#pPaQ#P;ro#BU7BT|E*%>%TNH_)e%+KH zg9nLHf&RmAg&l z$ZAX#C8_w27KoWu>Dr2!p@UtgEK0oZ8ve>vL$37r?K^kQCphw=vwKSi*8;f@uN)aJ z6#f0AN)`7!lkwVqo+Fr?3|rJQPa*0U5@aV|CsV(78TOL~k_S#oUH0h%N0ui|SFc&u zwWEr-QQ=YWr}Ckh-ESpo6!qzlJn7sLI0`;GjD9RI%`sZc>r!_7twd#H0?P9lL&G(C z1K$4y4vT(Y@()~O%_DT@JVrEAHCGSpC=Gx2_bUwfLdBTg8-KcTH23+x`7M7ObW4qw zc06*Z@8Q0OpTe(skd7hzPntSorDx|)Y?p04eQLs%Eg)FbVf%{Hw@?0XHLI5Y@%v%X zhegx=nCZT=vIN4`myQ#1{ahCV%4Fa7NNTZ5RpvfIM1g*^V# zb91g-vu@!2BlDXI&_yeU4z+uHx^(aR?dXRVXe@v6h2skU9)fM3N8P29r%v5(_%{a* zZ(GxM|Iy_?4z3D1zxVvWx+{xfOzICoxF7UfzFt~<^V%KfDtJ~C z8}F{vPTV8)PHO$e8%v&UhyV8S;5f&3q0yK8`4_s#;E$3&|B09WA6TcY{(&&#Sa zK5%uP!TH;V3O)+bZ&u3cvb83n0-xNzd2?_4s=sEajvdn4v>@-ZSlM*!@sTK|6*r#8 z9TsKsX(%aZwy^)iyy;KLiZ1hGQ#cx?sFc-dYh9B^`Rlf6efO^Ce))Pf{2v92a(aXq0e1jx4x@an6H4G(u=rp>IvsR{< zdPT2KYu+4F*PF_CJU&uGbMaA*-DWZzC5lP*U@?B|x^*cu+)sW2au^k#g0cYR_g_zNXWvy~fc1|=Q~ zQ?Fmo_IVaJb3FbUWyrl^>4{{rIG2IYoMdQIQhdk)5R;^-S?0``DNaXZI8XR zhNsL#=+QlXcb7`KdU`y-(APhw?%Dc4{%FXx53Y1vL*=GQp~lnKP5mFMPLIgoF2}or z<%hULcYP2_WGLQ5tV6}{@LWv`UjrO2k3m(D46^h+N8#|6V}&V zk$|!Y(J@@IWhky#@Anlm4^{Ho^fO2HW>o^^k(#}0Dv@KvrRu7LjoA?ynZSdzEg4ad zf8G#2c$(w06J|jFql+T`{i@=-HZ>{H&J-B6t_dkNJWBt@dD5Iw%yFA@0W;=)%b=OP zF2}}nfRCqOLdAbruh7*ga}<=%ef##IERj8uSaN!Cfn$IVQ^--U&ogM=(EjzKL{6fW zSZei(@96u~Ofg!6h`{>Of#+l7wvcsI=#M5?8Lyq&w%V7TcO{9T+q}6LHSd;bJ>zx1 z_VY|#dmWI8x#~i#xpti2rdkSI9K_J}D=d0jz|$55^m% z#3u$_9XJs)s%)d#jL<6v%i|UxWD-j=#va38b!y!&*OiNB1z7TVrI0Mj@cRe-K=7TnU@<2xK@*4C>_BA-%F+e4)Qn=79FcM-=UrTipSjhv2 z53dC$Ob)EH6;v=kWdM1Um?eJhz`po7A6CxXUT?5v?JAO?hL*~AhDV?3=G^H0@_H9g zOc$q9cLr(DO*@7fPCq}kYZmAR9t=SASD+?#5QKDqmuP59MSfKvei9!nKGH;d$ z(ByKG=4WOc;btQGt4-`3YA;|wIpKS>UnEM>4O$MPpz4&868wBtR+zpGjayI1=NP$s z><^G!I)N&;L>BX`Yi;jQBe!v97^l;K00TX}Cn^lPMB6VN`>`-Pj!9=*;jrftb_3lb z`HY|Z3chJdNnl!g%U!SsJvu!?x~&n35po(Mncub9*2cQo*H#~bPRQkWQMCJ>oc(`g}WZrQ@1qmLfv-r5OSwZ_SCE#IE>A?y>M^mIO7X7N2+7z9>R z$;inWp~{@x7MOh1eclDvvYLv|s-1CudbkV(hK;(dc@Po~cJ>YMYfnOU(f@-A>Lb^6 zl71$BOTdN)fDAQ#C?z(863D*MrEsge>(sTuwo=AL-}-?`%>rkB^DnQ0hfTGV@_7Ru zSB%!5;UPDlWA|gzH)zbVXowJV+D>&Ib~dA=J0ogtk6gDNz9N~%8a35@jGf($l|<9l z7P`4SkB!~O#zwq2QSqZ<*7nf3=_)dq09D8&N(UW^E-u(!Z<$p;jMg&`r8)kbEW;<9)2dMd2NMpU2Q0i1BJvHS`3r1FCJ`2qAL!yz9*@g@}&IsXkC&Iqn1 z{ZYf44HY4~qPT|CnB-6JyRLY?Zuw-OOh}eNlCH3Bp8Xb*3kl?=G6BYHJ_>=GMB-w`8X!nY@}jSj+TiWuTEBUF|IfxMckK2LWp4#u!REw z8!s&!of;qifL0_X^9l=rPV_m{Ug1MK)i$L_F)#Eje7JNB1VmBBFwRVmv(PTm+S+>n zL%2o9MQFb@Jo+88No0T*y^g4(Yex}z?%~-%-=s1tlP)M4i|XE7l%BvAJYoILCrb-c zetW8!2CfnW5mr#^<>i%)nD!AmAG>7hQ>Q#Xj9VWR^tUkfcb7}lFW7_J1ijGpy{I-7 z_XzpGFs>PQb~nmTSAl~m^?|szMHve{@#|aDLv7 z-oIMvP4{P-_Rw=w>#3>ly)%4^BS9I$iEYg&d}DPNflcklAegF1%#N90C^OkF)_5_) z{0mU+Rg+f;eX%yvw7`QK!UQaDYbSD_w;oOuwimSO&Dyeu@5I@&n=`Mqd4tEOv#GN3 z&Z()f%GE%g4+0VECW}4N{o){C#sv~t^KL|=dtrx z#Lhgjv%r$)X*^+~U%?%+?66fKN0&N6$pdGp@aIMt1^{h(qqAxM(WBe22gV<*44+QeO4T>Kr;+*68*vcPwZp!~aQehJ!}T;M48Id*i!95@yD z2gFl)r>q!Q3e-G0>g~BzhYug75J2Z-6uP6C8#T-82ChnNvnYLi;nm7EmJbhFg;M>5 z$9B^)F!@qJ9u7d^?vJl2#t03GAujc+c7gGi=~Q<^yY8zC92b&wcM}N61Avm5iQb)DF`lxI zUdi1J!THauaB(ZCNE-sDyD> z{v}X&{>A7^!n`==1cRuh^kl3c$&r4BFZ+Ec{kPt(GbpNc%QhK7auN^$kt<4S5D86A zwgCx>B0++RfFubbu@4fxNK#Y`WJD0f1|$nKf&>X7f|BX0NRvTwhS^8&d-bMf=EuCM z8UJWOb=!T;cfN1!wf0&Y4s;z@ap0D8N5gRe*8nfK9|Dol(Ly!t?XvwNw*OdM;9)}Z zj=`ZJ7k{_ARgvkb*^soRq<-@9Cxc$5wy;H6$8*_Aoy;3Z7-GAJOnqtdi_T;B$MW{* zR`wTwm+XjvVz_`R$7RAjKe6qx|yB99aF8>@$)_)^&>ipww2Ot5#J~@S; zGWuZm3wUfMSLnZqYtC84f7hvRx)BE)K5~vbAzz?ic`fAQS7*(-;nT;d8mB?;w4x0FsU- z6|xPp$YaP7JgI27G2DLpBLgO}i%0PDPDV1ox?pcim_`e0?gN7ddNpoIWxv_L1 zx`myz;KR!UC*W_7RSDZajFx*}DI%_YF#l@q!jN8h&QiY(VT}$RAss+C&{2+sr;QQH zjylM{O|p=2kN4Bt23Ps*J9l(}@XM=%p>;7~VYH@s7GEf7{8O8!?b(c0x|^Dg12|-W zSdVv*&T*~E#*E-VDGTWqF6T3x4)*I*%c+PB@pk%UX1ECF{@3IE_M9E`1Jw z|L@^SPFv6;?ybY%7)^)49GBiRq7l_#0;&gJi3cdMBEvJ2kM+;M(5^Q7v*^Ck;TU!- z)59@kc^O?L?lMO3Eg`HV9n5t(7R85m)6>KkNUJvUExZ7h{?Q{x>BU8VbxtlW=0Gx) zuQnNAu9}QZbu?7T7%e!0L3)BoZ7)lzh$KA+9!g^2)ZV9`Xues&4c z5`c6loUxK=ig6YiS*lmZJR%9RG+i;i;V1Y#uVp-ZYpXvT*M27JvVmPJ(?C#E^Eih1~$u?1ZYXf71b| z%>Y-abZB!JHfqIewl72*GUdsR!Lkb*7_Frsj3W|GvkZ8=l|8=G>Y21IJr@UmcCmHh zg?QB&2852z4lH1-Bw5+&>b8f6&oKd%eh>G-1Mr|rocS9wLb=Bk!ItKfcJPB7>#a;t za^Husg>n%fDNTb zY2mVwrsQ#rg%)r`)qDJC;y|Ftq?5KN%C}bSDYQQonfQ)x0qiMUAi2^o^Gbm-auke) zbhk6c7Lym%dzApKN4F%(UY;2!ckIf!do&v~1`kJmsF}=i8qhs5ka`_#WgK|O{;u7ZGudd!sLecP#?=Vf0gQbW!_oA z)!RY+Nze-pNSJqTk4}kz&5MzZjX|Diy_I^sV{-cLoI)a;$i2HI$5@utdTj(19D_j* zu!Ro=q1~LIrkx;rM=)I6XC5n6nj(I0aEQCR=)-eMacE> z&*DJRXF_#6)=f-p^Y*XO=wGrf9~`d+)eGH9!1NUY4!$4jNt1tXH2y(`CP7`vFQLGq z7e+SaAS&Ii>d-N$`%s@5Zn1Ts^(?Ya{RkS-28C{1Te3Qoq|YAE)`XSPwX#H8jqEGjR7A$Sw zKE0VaGLk5WFDbx}tnfeR+(Uj@^U5>)|v}98h%Lw zBzX*|X`{G{phM3ATowhA1?B-z$bZ+ckf39lSo$%mc;tUby$gtx(K?OwW`)j(UuITxzl6)u6m_ zoLj^#FK!Am%vd+JI(=K{!YwxpX!ys)y+VLK+2Nsmw>}F|%bmJlbo9&q{YN@?+#$nF8-`R(Zes=!5#Iik%{AlG&Gl%2Ss`_XGQCNPeKCdGA24TFIYQ?LvOE zUgL_b4jtcL2U5aReAsSM9;w`5Gg`EyD7ssl540Ta3ZZ1i#CJQ=)Ar0I%>>&vIV*(1 zPmC2jrh4P`^wvxcKM3D;IdiA}Oq%o04+vqVN$k<7Qaj+AZ;qhca6s7+V1xsFGsyC# zehYm^GaY;Aq9V%4CMma@F2bCh&!0bg22I_Qg!-9h%;d@y&6_`fT<*uF>fd=g|Co!? zqdxHXGNnsht;79sf2H5ZU0|x+GddhaPM||LnX|Yn^Eae7US6)8H$i}s zA8NM6)?AT)cd0;&yPmpKEU_is^e~*re*TXc$_~fb%2ZJ=m=~4Fr>fY+s>_Ot)BTa~ zVf{AN0hC8zTq#|lOfSjnCy}Lx_-}Jf>Ku1L(aSSK%Hx9`HD-jbNm;-*lm!3|yI2Zq zsMYPQuc|+v{6io>{>^q$R7^12))y-i8?4l>efij5SCW>gW z>xYG_fAii8eRch@{>_vdE=7_S;=fnhyqh1-3arnohi2#|Npai!r`eKYL;pWSr{5xu zhT-@)2JsZzyCvM}d{rx2o#B~UYruC{v2|kU6I&YGEN{ys_5OY`h=2VnYyaPp_Dshs z2m%jtMgTA|V#g+K`jD!RjN=EX{0J$IMrQZt@uC zsteqy3R~E)-M+!}1IemQ&oSl0q)pB9z7Ht1RjJ(9Ttf84>3P;be^>zoXn@43;5M)) zl%6HT!u73m!~6HPu#Mi1iKz!_YTgCNdvxv&1MA-4lUX`I$#uY~=F&Ox<$0BB{LRbz z?(o9%V5h;1wtosLu;IDvg^aG^R- zrG{WBF*OaG1r-Jyx3?i&nbPwR$Z}9001%=tKaoK8!fDJ(%}hU=K$NMQ`Lb)2t5x3j zBX28%rbih>B+t`-PQI|mfi2i$Nb-}xW@^jmFr)c}bF0#2e;o<0@^zDyBAt%B`eQ$% z!*=la+tM^bkjAs8e4^Gk#&a9pYC`)}Y*%Iml|6qj0v3r#OA~=eDR!Xv>meuSRj12P zB|jVmc&KyO1qG3(?oVL^14^YpTWbu^nFh%?9ymX8Y)I9Eu6>>Kewvg5xPWjSJD27+ z+};AyRN*rn1xNJI>*e{4*rB&KWl)mKS3qysiwAn{w|#JwdW z?)ts`kj}*nZ+HvNY%y{1dT}}b5vR3fW8ieZ{Gf!xl;aDuADf-VU%RRDz6FoLd>^SBClZsT?k^oo+2DH4Bdn|LLUkoe zEX_q}&wPF^mY~kvfYe`3_j6^vVq|*gzTeqzfI*pI>_adtZJ^rY~TWCgQgF-Q_wHL41G`2#!r`vw1?vSer>qnYu;6x)%~2DoW>R2 zPH+2LW>qb?_0gyzMi!Rv^Dp(GuAN&U?;t{Zy}SwcFA^q|Npt4n;^Nh$hKo> zb0y4>f@fn?Impvf9$--i(MEF;yKLF*c_S6ZV`;*Z5CMnGeW~#$Z|Z_F%FMcgK2nNv z7mnvd$Oe1Q@TarJ24d0t189$HtPHx0tYF`NTZa?5*3soC7^)usa-=yk{GD`Cswront zaS?j$*u(Rg)SbRhO|Vc0c_hoT&xG4!fpQv7N=SbTfSy?i2&h%MFKaV&wW6u1$t8Py z^cb;QOiqsDT<&IRLN*NJfq_8`7;^fRnb_mgXRXCv} z`2&D4l{UL)($|#PF(}N_VGhjKZa}8m;Y>VC3YyL41mxFs4-X4VgF3FK%tH*|1qjgS zoGU1XWgEwpT>HX+QZUUm-DOLX=3ZXzgoc53#>dm?7BcIUlhpfPJKws z>D>3^kfrF}UfSWs8RHxNT)F8BqT-u0bzzF}oJ1SzQppOxO+I2?9XHEoMDIPOYq zOp@nnh*PB|wueeRQ{cXl8fw4eZmhPT7B>8p>Z|~_v`vWb7U?MqrQa|-TSStF^&2r6 z6|O72*_#dBwUmp6atT}_NYVhSe&|OT8=Km3O5tjHK!;Ia+3Oup1(dC^h zY8gEhJ_$7;yFLdhJ3)GYNYs7^WC{naARRVhx@$+IrCESU6o_fSwZbqhsMPaZ<45J< z^qB#oS%fR$o{9s%Av^*94!H;k36C}x>g`{z+{1k#+MU=Rrt+!atZ zYGLME2y_Tj*7gNdXS%~y4ymB%a6m*6zsxKTPlDTI&(PdsnCE~7L)k$L%qj##unP!8 z0VgNk4^h8)x{w8jR)j(ggh@i&ayn#)U4pzW6~mWbuDuy)Y(%g-|F&Sl9_6$Caifdky&emwOMZ8IY@~kaOf+KW%*Y`pog^{xeHE zLytW0L389C8NQ|pF`e_3QW&kRb+y$t)dzDce4D&Uk!qE^7_E*R!L-jcg*&B-XXpE` zXo{0x`v~~l@}0wk=}xBSkU0tu9@gcx;Gm2oP?b|h^|@XwsSdltU}wFytE-Mwa%d_` z5^uL|r_dGZfN1ktciJzAtuTQ+LL!mcPHP`H5n^TiGmZn)0JJRbE8bae2GZ0^T|Y0s zPo({ykY=#`Qh_aK&5D|vJWTqpl_-TPsyI3)ud*2#%1%S6$Jq=@TM0?&C;FW_4w)#%L2 zC$Q3feBU2rvbRq~_-#!|O>6A(%R7(7BXn}#gtf4Bl?2}C1qK=^{uNqf&1PVHt$=Oj+(F)!M=Z28k2`?Qr)zu)n$97w<-%JZY3S~SHh?=oo~1(&(^scLLTcZF z&=$-RMH?>Q8kRUU4y~DN|AvfDI^kRl3k$P_0SSN%L7b@vfcZvf+y=VvK@|yy{odX= z4%zw|jGbuQS<($y{djZA?K^XZpAq_8z!F?%nc`|ynEgRQj+Mk?- zxx66h4+AW104+=uRsi(qfHW|aUo8vh9YolMI*p`R6~EoiL6`ZfDzUydJ7m_7AT0X#x1MMCxgdWwpDOT`{0EO z8GKsmfw$VW=x;}`Ac7HawM4PlPo3$YLas>>QhC)|U(DV+@PV=ur!i`M?*JO6kFvWk z6Ihf{r$jSzTr|aZ|4IP1r%p0%;N^yqt_9VdfOGDnE52nRa|wo+E5=ouyF{3<#HYK= z!!7hn@)dM7oqx76FOcSCDE-7n!wiL0I3<(`}S}3#I?gm^L7#Xx=S_+AOkVlWS z{g4J$;CB(fk4D3*H1O|=x0(OnvYd{3d&nkA8q2>w`tFz4fP+Kb`ashNQ?{i?MVZ+P z(;N;_6`{X}lXnasL%8X3Os{44!!K7>*z|T-*+dgmgqVM|opRi5k3VpzamOJ@`ox(M z55-RHU!KzfaHN9gH0SIZmG$IRPu$<5%JL2Os{7y9KuT*hgOV6c6zW{$bY7ZhXEXbp*o|6N6E9-j z6`qTVALY54!0}%ks99e{YBVtdF<b=&n4fzkP;AvX` diff --git a/frontend/src/scenes/heatmaps/HeatmapsBrowser.tsx b/frontend/src/scenes/heatmaps/HeatmapsBrowser.tsx index c1d395eb7ff8cc..af088a550cacd3 100644 --- a/frontend/src/scenes/heatmaps/HeatmapsBrowser.tsx +++ b/frontend/src/scenes/heatmaps/HeatmapsBrowser.tsx @@ -1,4 +1,4 @@ -import { IconCollapse } from '@posthog/icons' +import { IconCollapse, IconGear } from '@posthog/icons' import { LemonBanner, LemonButton, LemonInputSelect, LemonSkeleton, Spinner, Tooltip } from '@posthog/lemon-ui' import { BindLogic, useActions, useValues } from 'kea' import { AuthorizedUrlList } from 'lib/components/AuthorizedUrlList/AuthorizedUrlList' @@ -10,6 +10,9 @@ import { DetectiveHog } from 'lib/components/hedgehogs' import { useResizeObserver } from 'lib/hooks/useResizeObserver' import { IconChevronRight, IconOpenInNew } from 'lib/lemon-ui/icons' import React, { useEffect, useRef } from 'react' +import { teamLogic } from 'scenes/teamLogic' + +import { sidePanelSettingsLogic } from '~/layout/navigation-3000/sidepanel/panels/sidePanelSettingsLogic' import { heatmapsBrowserLogic } from './heatmapsBrowserLogic' @@ -260,6 +263,28 @@ function EmbeddedHeatmapBrowser({ ) : null } +function Warnings(): JSX.Element | null { + const { currentTeam } = useValues(teamLogic) + const heatmapsEnabled = currentTeam?.heatmaps_opt_in + + const { openSettingsPanel } = useActions(sidePanelSettingsLogic) + + return !heatmapsEnabled ? ( + , + onClick: () => openSettingsPanel({ settingId: 'heatmaps' }), + children: 'Configure', + }} + dismissKey="heatmaps-might-be-disabled-warning" + > + You aren't collecting heatmaps data. Enable heatmaps in your project. + + ) : null +} + export function HeatmapsBrowser(): JSX.Element { const iframeRef = useRef(null) @@ -271,7 +296,8 @@ export function HeatmapsBrowser(): JSX.Element { return ( -

+
+
diff --git a/frontend/src/scenes/scenes.ts b/frontend/src/scenes/scenes.ts index 3fc5293e0beda4..7d44fe0927b7e1 100644 --- a/frontend/src/scenes/scenes.ts +++ b/frontend/src/scenes/scenes.ts @@ -408,7 +408,6 @@ export const sceneConfigurations: Record = { [Scene.Heatmaps]: { projectBased: true, name: 'Heatmaps', - hideProjectNotice: true, }, } From 134c4b0ef6573ee814f3eb182e17e92b7bd66548 Mon Sep 17 00:00:00 2001 From: Joe Martin <84011561+joethreepwood@users.noreply.github.com> Date: Tue, 11 Jun 2024 09:13:24 +0100 Subject: [PATCH 25/35] chore: Update readme for person profiles (#22844) Update for person profiles --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f261c5f98d4664..fcdeaa17cdadcd 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,15 @@ PostHog Demonstration - See PostHog in action +
See PostHog in action

## PostHog is an all-in-one, open source platform for building better products - Specify events manually, or use autocapture to get started quickly - Analyze data with ready-made visualizations, or do it yourself with SQL +- Only capture properties on the people you want to track, save money when you don't - Gather insights by capturing session replays, console logs, and network monitoring - Improve your product with A/B testing that automatically analyzes performance - Safely roll out features to select users or cohorts with feature flags @@ -38,7 +40,7 @@ PostHog is available with hosting in the EU or US and is fully SOC 2 compliant. - 1 million feature flag requests - 250 survey responses -We're constantly adding new features, with web analytics and data warehouse now in beta! +We're constantly adding new features, with web analytics and data warehouse now in beta! ## Table of Contents @@ -73,7 +75,7 @@ PostHog brings all the tools and data you need to build better products. ### Analytics and optimization tools - **Event-based analytics:** Capture your product's usage [automatically](https://posthog.com/docs/libraries/js#autocapture), or [customize](https://posthog.com/docs/getting-started/install) it to your needs -- **User and group tracking:** Understand the [people](https://posthog.com/manual/persons) and [groups](https://posthog.com/manual/group-analytics) behind the events and track properties about them +- **User and group tracking:** Understand the [people](https://posthog.com/manual/persons) and [groups](https://posthog.com/manual/group-analytics) behind the events and track properties about them when needed - **Data visualizations:** Create and share [graphs](https://posthog.com/docs/features/trends), [funnels](https://posthog.com/docs/features/funnels), [paths](https://posthog.com/docs/features/paths), [retention](https://posthog.com/docs/features/retention), and [dashboards](https://posthog.com/docs/features/dashboards) - **SQL access:** Use [SQL](https://posthog.com/docs/product-analytics/sql) to get a deeper understanding of your users, breakdown information and create completely tailored visualizations - **Session replays:** [Watch videos](https://posthog.com/docs/features/session-recording) of your users' behavior, with fine-grained filters and privacy controls, as well as network monitoring and captured console logs From a98ed9281eb8e21fff0641754265d4ba7b23768a Mon Sep 17 00:00:00 2001 From: Marius Andra Date: Tue, 11 Jun 2024 11:32:03 +0200 Subject: [PATCH 26/35] feat(hogql): window functions with params and args (#22862) * feat(hogql): window functions with params and args * Use new hogql-parser version --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- hogql_parser/HogQLParser.cpp | 1157 ++++++++-------- hogql_parser/HogQLParser.h | 10 +- hogql_parser/HogQLParser.interp | 2 +- hogql_parser/parser.cpp | 27 +- hogql_parser/setup.py | 2 +- posthog/hogql/ast.py | 1 + posthog/hogql/grammar/HogQLParser.g4 | 6 +- posthog/hogql/grammar/HogQLParser.interp | 2 +- posthog/hogql/grammar/HogQLParser.py | 1184 +++++++++-------- posthog/hogql/parser.py | 6 +- posthog/hogql/printer.py | 5 +- posthog/hogql/test/_test_parser.py | 30 +- posthog/hogql/test/test_printer.py | 8 + posthog/hogql/visitor.py | 7 +- .../hogql_queries/insights/trends/display.py | 2 +- requirements.in | 2 +- requirements.txt | 2 +- 17 files changed, 1348 insertions(+), 1105 deletions(-) diff --git a/hogql_parser/HogQLParser.cpp b/hogql_parser/HogQLParser.cpp index ff90358df9bd27..112fd7cb48ae0e 100644 --- a/hogql_parser/HogQLParser.cpp +++ b/hogql_parser/HogQLParser.cpp @@ -113,7 +113,7 @@ void hogqlparserParserInitialize() { } ); static const int32_t serializedATNSegment[] = { - 4,1,154,1158,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6, + 4,1,154,1178,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6, 2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14, 7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21, 7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,2,26,7,26,2,27,7,27,2,28, @@ -171,52 +171,54 @@ void hogqlparserParserInitialize() { 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, 1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,724,8,53,1,53,1,53,1,53,1,53, 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,741,8,53, - 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,753,8,53,1,53, - 1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,763,8,53,1,53,3,53,766,8,53,1, - 53,1,53,3,53,770,8,53,1,53,3,53,773,8,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,1,53,1,53,1,53,3,53,787,8,53,1,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,804,8,53,1,53, - 1,53,1,53,3,53,809,8,53,1,53,1,53,3,53,813,8,53,1,53,1,53,1,53,1,53,3, - 53,819,8,53,1,53,1,53,1,53,1,53,1,53,3,53,826,8,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,1,53,1,53,1,53,3,53,838,8,53,1,53,1,53,3,53,842,8,53,1, - 53,3,53,845,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,854,8,53,1,53, - 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,868,8,53, + 1,53,1,53,1,53,1,53,3,53,747,8,53,1,53,3,53,750,8,53,1,53,3,53,753,8, + 53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,763,8,53,1,53,1,53,1, + 53,1,53,3,53,769,8,53,1,53,3,53,772,8,53,1,53,3,53,775,8,53,1,53,1,53, + 1,53,1,53,1,53,1,53,3,53,783,8,53,1,53,3,53,786,8,53,1,53,1,53,3,53,790, + 8,53,1,53,3,53,793,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, + 1,53,1,53,1,53,3,53,807,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, + 1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,824,8,53,1,53,1,53,1,53,3,53, + 829,8,53,1,53,1,53,3,53,833,8,53,1,53,1,53,1,53,1,53,3,53,839,8,53,1, + 53,1,53,1,53,1,53,1,53,3,53,846,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1, + 53,1,53,1,53,1,53,3,53,858,8,53,1,53,1,53,3,53,862,8,53,1,53,3,53,865, + 8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,874,8,53,1,53,1,53,1,53, + 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,888,8,53,1,53,1,53, 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,895,8,53, - 1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,904,8,53,5,53,906,8,53,10,53, - 12,53,909,9,53,1,54,1,54,1,54,5,54,914,8,54,10,54,12,54,917,9,54,1,55, - 1,55,3,55,921,8,55,1,56,1,56,1,56,1,56,5,56,927,8,56,10,56,12,56,930, - 9,56,1,56,1,56,1,56,1,56,1,56,5,56,937,8,56,10,56,12,56,940,9,56,3,56, - 942,8,56,1,56,1,56,1,56,1,57,1,57,1,57,5,57,950,8,57,10,57,12,57,953, - 9,57,1,57,1,57,1,57,1,57,1,57,1,57,5,57,961,8,57,10,57,12,57,964,9,57, - 1,57,1,57,3,57,968,8,57,1,57,1,57,1,57,1,57,1,57,3,57,975,8,57,1,58,1, - 58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,3,58,988,8,58,1,59,1, - 59,1,59,5,59,993,8,59,10,59,12,59,996,9,59,1,60,1,60,1,60,1,60,1,60,1, - 60,1,60,1,60,1,60,1,60,3,60,1008,8,60,1,61,1,61,1,61,1,61,3,61,1014,8, - 61,1,61,3,61,1017,8,61,1,62,1,62,1,62,5,62,1022,8,62,10,62,12,62,1025, - 9,62,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,3,63,1036,8,63,1,63, - 1,63,1,63,1,63,3,63,1042,8,63,5,63,1044,8,63,10,63,12,63,1047,9,63,1, - 64,1,64,1,64,3,64,1052,8,64,1,64,1,64,1,65,1,65,1,65,3,65,1059,8,65,1, - 65,1,65,1,66,1,66,1,66,5,66,1066,8,66,10,66,12,66,1069,9,66,1,67,1,67, - 1,68,1,68,1,68,1,68,1,68,1,68,3,68,1079,8,68,3,68,1081,8,68,1,69,3,69, - 1084,8,69,1,69,1,69,1,69,1,69,1,69,1,69,3,69,1092,8,69,1,70,1,70,1,70, - 3,70,1097,8,70,1,71,1,71,1,72,1,72,1,73,1,73,1,74,1,74,3,74,1107,8,74, - 1,75,1,75,1,75,3,75,1112,8,75,1,76,1,76,1,76,1,76,1,77,1,77,1,77,1,77, - 1,78,1,78,3,78,1124,8,78,1,79,1,79,5,79,1128,8,79,10,79,12,79,1131,9, - 79,1,79,1,79,1,80,1,80,1,80,1,80,1,80,3,80,1140,8,80,1,81,1,81,5,81,1144, - 8,81,10,81,12,81,1147,9,81,1,81,1,81,1,82,1,82,1,82,1,82,1,82,3,82,1156, - 8,82,1,82,0,3,68,106,126,83,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30, - 32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76, - 78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116, - 118,120,122,124,126,128,130,132,134,136,138,140,142,144,146,148,150,152, - 154,156,158,160,162,164,0,16,2,0,17,17,72,72,2,0,42,42,49,49,3,0,1,1, - 4,4,8,8,4,0,1,1,3,4,8,8,78,78,2,0,49,49,71,71,2,0,1,1,4,4,2,0,7,7,21, - 22,2,0,28,28,47,47,2,0,69,69,74,74,3,0,10,10,48,48,87,87,2,0,39,39,51, - 51,1,0,103,104,2,0,114,114,134,134,7,0,20,20,36,36,53,54,68,68,76,76, - 93,93,99,99,12,0,1,19,21,28,30,35,37,40,42,49,51,52,56,56,58,67,69,75, - 77,92,94,95,97,98,4,0,19,19,28,28,37,37,46,46,1288,0,169,1,0,0,0,2,176, - 1,0,0,0,4,178,1,0,0,0,6,180,1,0,0,0,8,189,1,0,0,0,10,195,1,0,0,0,12,212, - 1,0,0,0,14,214,1,0,0,0,16,217,1,0,0,0,18,226,1,0,0,0,20,232,1,0,0,0,22, + 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,915,8,53,1,53,1,53, + 1,53,1,53,1,53,1,53,1,53,3,53,924,8,53,5,53,926,8,53,10,53,12,53,929, + 9,53,1,54,1,54,1,54,5,54,934,8,54,10,54,12,54,937,9,54,1,55,1,55,3,55, + 941,8,55,1,56,1,56,1,56,1,56,5,56,947,8,56,10,56,12,56,950,9,56,1,56, + 1,56,1,56,1,56,1,56,5,56,957,8,56,10,56,12,56,960,9,56,3,56,962,8,56, + 1,56,1,56,1,56,1,57,1,57,1,57,5,57,970,8,57,10,57,12,57,973,9,57,1,57, + 1,57,1,57,1,57,1,57,1,57,5,57,981,8,57,10,57,12,57,984,9,57,1,57,1,57, + 3,57,988,8,57,1,57,1,57,1,57,1,57,1,57,3,57,995,8,57,1,58,1,58,1,58,1, + 58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,3,58,1008,8,58,1,59,1,59,1,59,5, + 59,1013,8,59,10,59,12,59,1016,9,59,1,60,1,60,1,60,1,60,1,60,1,60,1,60, + 1,60,1,60,1,60,3,60,1028,8,60,1,61,1,61,1,61,1,61,3,61,1034,8,61,1,61, + 3,61,1037,8,61,1,62,1,62,1,62,5,62,1042,8,62,10,62,12,62,1045,9,62,1, + 63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,3,63,1056,8,63,1,63,1,63,1, + 63,1,63,3,63,1062,8,63,5,63,1064,8,63,10,63,12,63,1067,9,63,1,64,1,64, + 1,64,3,64,1072,8,64,1,64,1,64,1,65,1,65,1,65,3,65,1079,8,65,1,65,1,65, + 1,66,1,66,1,66,5,66,1086,8,66,10,66,12,66,1089,9,66,1,67,1,67,1,68,1, + 68,1,68,1,68,1,68,1,68,3,68,1099,8,68,3,68,1101,8,68,1,69,3,69,1104,8, + 69,1,69,1,69,1,69,1,69,1,69,1,69,3,69,1112,8,69,1,70,1,70,1,70,3,70,1117, + 8,70,1,71,1,71,1,72,1,72,1,73,1,73,1,74,1,74,3,74,1127,8,74,1,75,1,75, + 1,75,3,75,1132,8,75,1,76,1,76,1,76,1,76,1,77,1,77,1,77,1,77,1,78,1,78, + 3,78,1144,8,78,1,79,1,79,5,79,1148,8,79,10,79,12,79,1151,9,79,1,79,1, + 79,1,80,1,80,1,80,1,80,1,80,3,80,1160,8,80,1,81,1,81,5,81,1164,8,81,10, + 81,12,81,1167,9,81,1,81,1,81,1,82,1,82,1,82,1,82,1,82,3,82,1176,8,82, + 1,82,0,3,68,106,126,83,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32, + 34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78, + 80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118, + 120,122,124,126,128,130,132,134,136,138,140,142,144,146,148,150,152,154, + 156,158,160,162,164,0,16,2,0,17,17,72,72,2,0,42,42,49,49,3,0,1,1,4,4, + 8,8,4,0,1,1,3,4,8,8,78,78,2,0,49,49,71,71,2,0,1,1,4,4,2,0,7,7,21,22,2, + 0,28,28,47,47,2,0,69,69,74,74,3,0,10,10,48,48,87,87,2,0,39,39,51,51,1, + 0,103,104,2,0,114,114,134,134,7,0,20,20,36,36,53,54,68,68,76,76,93,93, + 99,99,12,0,1,19,21,28,30,35,37,40,42,49,51,52,56,56,58,67,69,75,77,92, + 94,95,97,98,4,0,19,19,28,28,37,37,46,46,1314,0,169,1,0,0,0,2,176,1,0, + 0,0,4,178,1,0,0,0,6,180,1,0,0,0,8,189,1,0,0,0,10,195,1,0,0,0,12,212,1, + 0,0,0,14,214,1,0,0,0,16,217,1,0,0,0,18,226,1,0,0,0,20,232,1,0,0,0,22, 236,1,0,0,0,24,245,1,0,0,0,26,247,1,0,0,0,28,256,1,0,0,0,30,260,1,0,0, 0,32,271,1,0,0,0,34,275,1,0,0,0,36,290,1,0,0,0,38,293,1,0,0,0,40,342, 1,0,0,0,42,345,1,0,0,0,44,351,1,0,0,0,46,355,1,0,0,0,48,361,1,0,0,0,50, @@ -226,14 +228,14 @@ void hogqlparserParserInitialize() { 541,1,0,0,0,80,549,1,0,0,0,82,567,1,0,0,0,84,569,1,0,0,0,86,577,1,0,0, 0,88,582,1,0,0,0,90,590,1,0,0,0,92,594,1,0,0,0,94,598,1,0,0,0,96,607, 1,0,0,0,98,621,1,0,0,0,100,623,1,0,0,0,102,673,1,0,0,0,104,675,1,0,0, - 0,106,812,1,0,0,0,108,910,1,0,0,0,110,920,1,0,0,0,112,941,1,0,0,0,114, - 974,1,0,0,0,116,987,1,0,0,0,118,989,1,0,0,0,120,1007,1,0,0,0,122,1016, - 1,0,0,0,124,1018,1,0,0,0,126,1035,1,0,0,0,128,1048,1,0,0,0,130,1058,1, - 0,0,0,132,1062,1,0,0,0,134,1070,1,0,0,0,136,1080,1,0,0,0,138,1083,1,0, - 0,0,140,1096,1,0,0,0,142,1098,1,0,0,0,144,1100,1,0,0,0,146,1102,1,0,0, - 0,148,1106,1,0,0,0,150,1111,1,0,0,0,152,1113,1,0,0,0,154,1117,1,0,0,0, - 156,1123,1,0,0,0,158,1125,1,0,0,0,160,1139,1,0,0,0,162,1141,1,0,0,0,164, - 1155,1,0,0,0,166,168,3,2,1,0,167,166,1,0,0,0,168,171,1,0,0,0,169,167, + 0,106,832,1,0,0,0,108,930,1,0,0,0,110,940,1,0,0,0,112,961,1,0,0,0,114, + 994,1,0,0,0,116,1007,1,0,0,0,118,1009,1,0,0,0,120,1027,1,0,0,0,122,1036, + 1,0,0,0,124,1038,1,0,0,0,126,1055,1,0,0,0,128,1068,1,0,0,0,130,1078,1, + 0,0,0,132,1082,1,0,0,0,134,1090,1,0,0,0,136,1100,1,0,0,0,138,1103,1,0, + 0,0,140,1116,1,0,0,0,142,1118,1,0,0,0,144,1120,1,0,0,0,146,1122,1,0,0, + 0,148,1126,1,0,0,0,150,1131,1,0,0,0,152,1133,1,0,0,0,154,1137,1,0,0,0, + 156,1143,1,0,0,0,158,1145,1,0,0,0,160,1159,1,0,0,0,162,1161,1,0,0,0,164, + 1175,1,0,0,0,166,168,3,2,1,0,167,166,1,0,0,0,168,171,1,0,0,0,169,167, 1,0,0,0,169,170,1,0,0,0,170,172,1,0,0,0,171,169,1,0,0,0,172,173,5,0,0, 1,173,1,1,0,0,0,174,177,3,6,3,0,175,177,3,12,6,0,176,174,1,0,0,0,176, 175,1,0,0,0,177,3,1,0,0,0,178,179,3,106,53,0,179,5,1,0,0,0,180,181,5, @@ -384,158 +386,165 @@ void hogqlparserParserInitialize() { 688,689,5,94,0,0,689,690,3,106,53,0,690,691,5,81,0,0,691,692,3,106,53, 0,692,694,1,0,0,0,693,688,1,0,0,0,694,695,1,0,0,0,695,693,1,0,0,0,695, 696,1,0,0,0,696,699,1,0,0,0,697,698,5,24,0,0,698,700,3,106,53,0,699,697, - 1,0,0,0,699,700,1,0,0,0,700,701,1,0,0,0,701,702,5,25,0,0,702,813,1,0, + 1,0,0,0,699,700,1,0,0,0,700,701,1,0,0,0,701,702,5,25,0,0,702,833,1,0, 0,0,703,704,5,13,0,0,704,705,5,126,0,0,705,706,3,106,53,0,706,707,5,6, - 0,0,707,708,3,102,51,0,708,709,5,144,0,0,709,813,1,0,0,0,710,711,5,19, - 0,0,711,813,5,106,0,0,712,713,5,43,0,0,713,714,3,106,53,0,714,715,3,142, - 71,0,715,813,1,0,0,0,716,717,5,80,0,0,717,718,5,126,0,0,718,719,3,106, + 0,0,707,708,3,102,51,0,708,709,5,144,0,0,709,833,1,0,0,0,710,711,5,19, + 0,0,711,833,5,106,0,0,712,713,5,43,0,0,713,714,3,106,53,0,714,715,3,142, + 71,0,715,833,1,0,0,0,716,717,5,80,0,0,717,718,5,126,0,0,718,719,3,106, 53,0,719,720,5,32,0,0,720,723,3,106,53,0,721,722,5,31,0,0,722,724,3,106, 53,0,723,721,1,0,0,0,723,724,1,0,0,0,724,725,1,0,0,0,725,726,5,144,0, - 0,726,813,1,0,0,0,727,728,5,83,0,0,728,813,5,106,0,0,729,730,5,88,0,0, + 0,726,833,1,0,0,0,727,728,5,83,0,0,728,833,5,106,0,0,729,730,5,88,0,0, 730,731,5,126,0,0,731,732,7,9,0,0,732,733,3,156,78,0,733,734,5,32,0,0, - 734,735,3,106,53,0,735,736,5,144,0,0,736,813,1,0,0,0,737,738,3,150,75, + 734,735,3,106,53,0,735,736,5,144,0,0,736,833,1,0,0,0,737,738,3,150,75, 0,738,740,5,126,0,0,739,741,3,104,52,0,740,739,1,0,0,0,740,741,1,0,0, - 0,741,742,1,0,0,0,742,743,5,144,0,0,743,744,1,0,0,0,744,745,5,64,0,0, - 745,746,5,126,0,0,746,747,3,88,44,0,747,748,5,144,0,0,748,813,1,0,0,0, - 749,750,3,150,75,0,750,752,5,126,0,0,751,753,3,104,52,0,752,751,1,0,0, - 0,752,753,1,0,0,0,753,754,1,0,0,0,754,755,5,144,0,0,755,756,1,0,0,0,756, - 757,5,64,0,0,757,758,3,150,75,0,758,813,1,0,0,0,759,765,3,150,75,0,760, - 762,5,126,0,0,761,763,3,104,52,0,762,761,1,0,0,0,762,763,1,0,0,0,763, - 764,1,0,0,0,764,766,5,144,0,0,765,760,1,0,0,0,765,766,1,0,0,0,766,767, - 1,0,0,0,767,769,5,126,0,0,768,770,5,23,0,0,769,768,1,0,0,0,769,770,1, - 0,0,0,770,772,1,0,0,0,771,773,3,108,54,0,772,771,1,0,0,0,772,773,1,0, - 0,0,773,774,1,0,0,0,774,775,5,144,0,0,775,813,1,0,0,0,776,813,3,114,57, - 0,777,813,3,158,79,0,778,813,3,140,70,0,779,780,5,114,0,0,780,813,3,106, - 53,19,781,782,5,56,0,0,782,813,3,106,53,13,783,784,3,130,65,0,784,785, - 5,116,0,0,785,787,1,0,0,0,786,783,1,0,0,0,786,787,1,0,0,0,787,788,1,0, - 0,0,788,813,5,108,0,0,789,790,5,126,0,0,790,791,3,34,17,0,791,792,5,144, - 0,0,792,813,1,0,0,0,793,794,5,126,0,0,794,795,3,106,53,0,795,796,5,144, - 0,0,796,813,1,0,0,0,797,798,5,126,0,0,798,799,3,104,52,0,799,800,5,144, - 0,0,800,813,1,0,0,0,801,803,5,125,0,0,802,804,3,104,52,0,803,802,1,0, - 0,0,803,804,1,0,0,0,804,805,1,0,0,0,805,813,5,143,0,0,806,808,5,124,0, - 0,807,809,3,30,15,0,808,807,1,0,0,0,808,809,1,0,0,0,809,810,1,0,0,0,810, - 813,5,142,0,0,811,813,3,122,61,0,812,683,1,0,0,0,812,703,1,0,0,0,812, - 710,1,0,0,0,812,712,1,0,0,0,812,716,1,0,0,0,812,727,1,0,0,0,812,729,1, - 0,0,0,812,737,1,0,0,0,812,749,1,0,0,0,812,759,1,0,0,0,812,776,1,0,0,0, - 812,777,1,0,0,0,812,778,1,0,0,0,812,779,1,0,0,0,812,781,1,0,0,0,812,786, - 1,0,0,0,812,789,1,0,0,0,812,793,1,0,0,0,812,797,1,0,0,0,812,801,1,0,0, - 0,812,806,1,0,0,0,812,811,1,0,0,0,813,907,1,0,0,0,814,818,10,18,0,0,815, - 819,5,108,0,0,816,819,5,146,0,0,817,819,5,133,0,0,818,815,1,0,0,0,818, - 816,1,0,0,0,818,817,1,0,0,0,819,820,1,0,0,0,820,906,3,106,53,19,821,825, - 10,17,0,0,822,826,5,134,0,0,823,826,5,114,0,0,824,826,5,113,0,0,825,822, - 1,0,0,0,825,823,1,0,0,0,825,824,1,0,0,0,826,827,1,0,0,0,827,906,3,106, - 53,18,828,853,10,16,0,0,829,854,5,117,0,0,830,854,5,118,0,0,831,854,5, - 129,0,0,832,854,5,127,0,0,833,854,5,128,0,0,834,854,5,119,0,0,835,854, - 5,120,0,0,836,838,5,56,0,0,837,836,1,0,0,0,837,838,1,0,0,0,838,839,1, - 0,0,0,839,841,5,40,0,0,840,842,5,14,0,0,841,840,1,0,0,0,841,842,1,0,0, - 0,842,854,1,0,0,0,843,845,5,56,0,0,844,843,1,0,0,0,844,845,1,0,0,0,845, - 846,1,0,0,0,846,854,7,10,0,0,847,854,5,140,0,0,848,854,5,141,0,0,849, - 854,5,131,0,0,850,854,5,122,0,0,851,854,5,123,0,0,852,854,5,130,0,0,853, - 829,1,0,0,0,853,830,1,0,0,0,853,831,1,0,0,0,853,832,1,0,0,0,853,833,1, - 0,0,0,853,834,1,0,0,0,853,835,1,0,0,0,853,837,1,0,0,0,853,844,1,0,0,0, - 853,847,1,0,0,0,853,848,1,0,0,0,853,849,1,0,0,0,853,850,1,0,0,0,853,851, - 1,0,0,0,853,852,1,0,0,0,854,855,1,0,0,0,855,906,3,106,53,17,856,857,10, - 14,0,0,857,858,5,132,0,0,858,906,3,106,53,15,859,860,10,12,0,0,860,861, - 5,2,0,0,861,906,3,106,53,13,862,863,10,11,0,0,863,864,5,61,0,0,864,906, - 3,106,53,12,865,867,10,10,0,0,866,868,5,56,0,0,867,866,1,0,0,0,867,868, - 1,0,0,0,868,869,1,0,0,0,869,870,5,9,0,0,870,871,3,106,53,0,871,872,5, - 2,0,0,872,873,3,106,53,11,873,906,1,0,0,0,874,875,10,9,0,0,875,876,5, - 135,0,0,876,877,3,106,53,0,877,878,5,111,0,0,878,879,3,106,53,9,879,906, - 1,0,0,0,880,881,10,22,0,0,881,882,5,125,0,0,882,883,3,106,53,0,883,884, - 5,143,0,0,884,906,1,0,0,0,885,886,10,21,0,0,886,887,5,116,0,0,887,906, - 5,104,0,0,888,889,10,20,0,0,889,890,5,116,0,0,890,906,3,150,75,0,891, - 892,10,15,0,0,892,894,5,44,0,0,893,895,5,56,0,0,894,893,1,0,0,0,894,895, - 1,0,0,0,895,896,1,0,0,0,896,906,5,57,0,0,897,903,10,8,0,0,898,904,3,148, - 74,0,899,900,5,6,0,0,900,904,3,150,75,0,901,902,5,6,0,0,902,904,5,106, - 0,0,903,898,1,0,0,0,903,899,1,0,0,0,903,901,1,0,0,0,904,906,1,0,0,0,905, - 814,1,0,0,0,905,821,1,0,0,0,905,828,1,0,0,0,905,856,1,0,0,0,905,859,1, - 0,0,0,905,862,1,0,0,0,905,865,1,0,0,0,905,874,1,0,0,0,905,880,1,0,0,0, - 905,885,1,0,0,0,905,888,1,0,0,0,905,891,1,0,0,0,905,897,1,0,0,0,906,909, - 1,0,0,0,907,905,1,0,0,0,907,908,1,0,0,0,908,107,1,0,0,0,909,907,1,0,0, - 0,910,915,3,110,55,0,911,912,5,112,0,0,912,914,3,110,55,0,913,911,1,0, - 0,0,914,917,1,0,0,0,915,913,1,0,0,0,915,916,1,0,0,0,916,109,1,0,0,0,917, - 915,1,0,0,0,918,921,3,112,56,0,919,921,3,106,53,0,920,918,1,0,0,0,920, - 919,1,0,0,0,921,111,1,0,0,0,922,923,5,126,0,0,923,928,3,150,75,0,924, - 925,5,112,0,0,925,927,3,150,75,0,926,924,1,0,0,0,927,930,1,0,0,0,928, - 926,1,0,0,0,928,929,1,0,0,0,929,931,1,0,0,0,930,928,1,0,0,0,931,932,5, - 144,0,0,932,942,1,0,0,0,933,938,3,150,75,0,934,935,5,112,0,0,935,937, - 3,150,75,0,936,934,1,0,0,0,937,940,1,0,0,0,938,936,1,0,0,0,938,939,1, - 0,0,0,939,942,1,0,0,0,940,938,1,0,0,0,941,922,1,0,0,0,941,933,1,0,0,0, - 942,943,1,0,0,0,943,944,5,107,0,0,944,945,3,106,53,0,945,113,1,0,0,0, - 946,947,5,128,0,0,947,951,3,150,75,0,948,950,3,116,58,0,949,948,1,0,0, - 0,950,953,1,0,0,0,951,949,1,0,0,0,951,952,1,0,0,0,952,954,1,0,0,0,953, - 951,1,0,0,0,954,955,5,146,0,0,955,956,5,120,0,0,956,975,1,0,0,0,957,958, - 5,128,0,0,958,962,3,150,75,0,959,961,3,116,58,0,960,959,1,0,0,0,961,964, - 1,0,0,0,962,960,1,0,0,0,962,963,1,0,0,0,963,965,1,0,0,0,964,962,1,0,0, - 0,965,967,5,120,0,0,966,968,3,114,57,0,967,966,1,0,0,0,967,968,1,0,0, - 0,968,969,1,0,0,0,969,970,5,128,0,0,970,971,5,146,0,0,971,972,3,150,75, - 0,972,973,5,120,0,0,973,975,1,0,0,0,974,946,1,0,0,0,974,957,1,0,0,0,975, - 115,1,0,0,0,976,977,3,150,75,0,977,978,5,118,0,0,978,979,3,156,78,0,979, - 988,1,0,0,0,980,981,3,150,75,0,981,982,5,118,0,0,982,983,5,124,0,0,983, - 984,3,106,53,0,984,985,5,142,0,0,985,988,1,0,0,0,986,988,3,150,75,0,987, - 976,1,0,0,0,987,980,1,0,0,0,987,986,1,0,0,0,988,117,1,0,0,0,989,994,3, - 120,60,0,990,991,5,112,0,0,991,993,3,120,60,0,992,990,1,0,0,0,993,996, - 1,0,0,0,994,992,1,0,0,0,994,995,1,0,0,0,995,119,1,0,0,0,996,994,1,0,0, - 0,997,998,3,150,75,0,998,999,5,6,0,0,999,1000,5,126,0,0,1000,1001,3,34, - 17,0,1001,1002,5,144,0,0,1002,1008,1,0,0,0,1003,1004,3,106,53,0,1004, - 1005,5,6,0,0,1005,1006,3,150,75,0,1006,1008,1,0,0,0,1007,997,1,0,0,0, - 1007,1003,1,0,0,0,1008,121,1,0,0,0,1009,1017,3,154,77,0,1010,1011,3,130, - 65,0,1011,1012,5,116,0,0,1012,1014,1,0,0,0,1013,1010,1,0,0,0,1013,1014, - 1,0,0,0,1014,1015,1,0,0,0,1015,1017,3,124,62,0,1016,1009,1,0,0,0,1016, - 1013,1,0,0,0,1017,123,1,0,0,0,1018,1023,3,150,75,0,1019,1020,5,116,0, - 0,1020,1022,3,150,75,0,1021,1019,1,0,0,0,1022,1025,1,0,0,0,1023,1021, - 1,0,0,0,1023,1024,1,0,0,0,1024,125,1,0,0,0,1025,1023,1,0,0,0,1026,1027, - 6,63,-1,0,1027,1036,3,130,65,0,1028,1036,3,128,64,0,1029,1030,5,126,0, - 0,1030,1031,3,34,17,0,1031,1032,5,144,0,0,1032,1036,1,0,0,0,1033,1036, - 3,114,57,0,1034,1036,3,154,77,0,1035,1026,1,0,0,0,1035,1028,1,0,0,0,1035, - 1029,1,0,0,0,1035,1033,1,0,0,0,1035,1034,1,0,0,0,1036,1045,1,0,0,0,1037, - 1041,10,3,0,0,1038,1042,3,148,74,0,1039,1040,5,6,0,0,1040,1042,3,150, - 75,0,1041,1038,1,0,0,0,1041,1039,1,0,0,0,1042,1044,1,0,0,0,1043,1037, - 1,0,0,0,1044,1047,1,0,0,0,1045,1043,1,0,0,0,1045,1046,1,0,0,0,1046,127, - 1,0,0,0,1047,1045,1,0,0,0,1048,1049,3,150,75,0,1049,1051,5,126,0,0,1050, - 1052,3,132,66,0,1051,1050,1,0,0,0,1051,1052,1,0,0,0,1052,1053,1,0,0,0, - 1053,1054,5,144,0,0,1054,129,1,0,0,0,1055,1056,3,134,67,0,1056,1057,5, - 116,0,0,1057,1059,1,0,0,0,1058,1055,1,0,0,0,1058,1059,1,0,0,0,1059,1060, - 1,0,0,0,1060,1061,3,150,75,0,1061,131,1,0,0,0,1062,1067,3,106,53,0,1063, - 1064,5,112,0,0,1064,1066,3,106,53,0,1065,1063,1,0,0,0,1066,1069,1,0,0, - 0,1067,1065,1,0,0,0,1067,1068,1,0,0,0,1068,133,1,0,0,0,1069,1067,1,0, - 0,0,1070,1071,3,150,75,0,1071,135,1,0,0,0,1072,1081,5,102,0,0,1073,1074, - 5,116,0,0,1074,1081,7,11,0,0,1075,1076,5,104,0,0,1076,1078,5,116,0,0, - 1077,1079,7,11,0,0,1078,1077,1,0,0,0,1078,1079,1,0,0,0,1079,1081,1,0, - 0,0,1080,1072,1,0,0,0,1080,1073,1,0,0,0,1080,1075,1,0,0,0,1081,137,1, - 0,0,0,1082,1084,7,12,0,0,1083,1082,1,0,0,0,1083,1084,1,0,0,0,1084,1091, - 1,0,0,0,1085,1092,3,136,68,0,1086,1092,5,103,0,0,1087,1092,5,104,0,0, - 1088,1092,5,105,0,0,1089,1092,5,41,0,0,1090,1092,5,55,0,0,1091,1085,1, - 0,0,0,1091,1086,1,0,0,0,1091,1087,1,0,0,0,1091,1088,1,0,0,0,1091,1089, - 1,0,0,0,1091,1090,1,0,0,0,1092,139,1,0,0,0,1093,1097,3,138,69,0,1094, - 1097,5,106,0,0,1095,1097,5,57,0,0,1096,1093,1,0,0,0,1096,1094,1,0,0,0, - 1096,1095,1,0,0,0,1097,141,1,0,0,0,1098,1099,7,13,0,0,1099,143,1,0,0, - 0,1100,1101,7,14,0,0,1101,145,1,0,0,0,1102,1103,7,15,0,0,1103,147,1,0, - 0,0,1104,1107,5,101,0,0,1105,1107,3,146,73,0,1106,1104,1,0,0,0,1106,1105, - 1,0,0,0,1107,149,1,0,0,0,1108,1112,5,101,0,0,1109,1112,3,142,71,0,1110, - 1112,3,144,72,0,1111,1108,1,0,0,0,1111,1109,1,0,0,0,1111,1110,1,0,0,0, - 1112,151,1,0,0,0,1113,1114,3,156,78,0,1114,1115,5,118,0,0,1115,1116,3, - 138,69,0,1116,153,1,0,0,0,1117,1118,5,124,0,0,1118,1119,3,150,75,0,1119, - 1120,5,142,0,0,1120,155,1,0,0,0,1121,1124,5,106,0,0,1122,1124,3,158,79, - 0,1123,1121,1,0,0,0,1123,1122,1,0,0,0,1124,157,1,0,0,0,1125,1129,5,137, - 0,0,1126,1128,3,160,80,0,1127,1126,1,0,0,0,1128,1131,1,0,0,0,1129,1127, - 1,0,0,0,1129,1130,1,0,0,0,1130,1132,1,0,0,0,1131,1129,1,0,0,0,1132,1133, - 5,139,0,0,1133,159,1,0,0,0,1134,1135,5,152,0,0,1135,1136,3,106,53,0,1136, - 1137,5,142,0,0,1137,1140,1,0,0,0,1138,1140,5,151,0,0,1139,1134,1,0,0, - 0,1139,1138,1,0,0,0,1140,161,1,0,0,0,1141,1145,5,138,0,0,1142,1144,3, - 164,82,0,1143,1142,1,0,0,0,1144,1147,1,0,0,0,1145,1143,1,0,0,0,1145,1146, - 1,0,0,0,1146,1148,1,0,0,0,1147,1145,1,0,0,0,1148,1149,5,0,0,1,1149,163, - 1,0,0,0,1150,1151,5,154,0,0,1151,1152,3,106,53,0,1152,1153,5,142,0,0, - 1153,1156,1,0,0,0,1154,1156,5,153,0,0,1155,1150,1,0,0,0,1155,1154,1,0, - 0,0,1156,165,1,0,0,0,135,169,176,185,200,212,224,240,251,265,271,281, + 0,741,742,1,0,0,0,742,743,5,144,0,0,743,752,1,0,0,0,744,746,5,126,0,0, + 745,747,5,23,0,0,746,745,1,0,0,0,746,747,1,0,0,0,747,749,1,0,0,0,748, + 750,3,108,54,0,749,748,1,0,0,0,749,750,1,0,0,0,750,751,1,0,0,0,751,753, + 5,144,0,0,752,744,1,0,0,0,752,753,1,0,0,0,753,754,1,0,0,0,754,755,5,64, + 0,0,755,756,5,126,0,0,756,757,3,88,44,0,757,758,5,144,0,0,758,833,1,0, + 0,0,759,760,3,150,75,0,760,762,5,126,0,0,761,763,3,104,52,0,762,761,1, + 0,0,0,762,763,1,0,0,0,763,764,1,0,0,0,764,765,5,144,0,0,765,774,1,0,0, + 0,766,768,5,126,0,0,767,769,5,23,0,0,768,767,1,0,0,0,768,769,1,0,0,0, + 769,771,1,0,0,0,770,772,3,108,54,0,771,770,1,0,0,0,771,772,1,0,0,0,772, + 773,1,0,0,0,773,775,5,144,0,0,774,766,1,0,0,0,774,775,1,0,0,0,775,776, + 1,0,0,0,776,777,5,64,0,0,777,778,3,150,75,0,778,833,1,0,0,0,779,785,3, + 150,75,0,780,782,5,126,0,0,781,783,3,104,52,0,782,781,1,0,0,0,782,783, + 1,0,0,0,783,784,1,0,0,0,784,786,5,144,0,0,785,780,1,0,0,0,785,786,1,0, + 0,0,786,787,1,0,0,0,787,789,5,126,0,0,788,790,5,23,0,0,789,788,1,0,0, + 0,789,790,1,0,0,0,790,792,1,0,0,0,791,793,3,108,54,0,792,791,1,0,0,0, + 792,793,1,0,0,0,793,794,1,0,0,0,794,795,5,144,0,0,795,833,1,0,0,0,796, + 833,3,114,57,0,797,833,3,158,79,0,798,833,3,140,70,0,799,800,5,114,0, + 0,800,833,3,106,53,19,801,802,5,56,0,0,802,833,3,106,53,13,803,804,3, + 130,65,0,804,805,5,116,0,0,805,807,1,0,0,0,806,803,1,0,0,0,806,807,1, + 0,0,0,807,808,1,0,0,0,808,833,5,108,0,0,809,810,5,126,0,0,810,811,3,34, + 17,0,811,812,5,144,0,0,812,833,1,0,0,0,813,814,5,126,0,0,814,815,3,106, + 53,0,815,816,5,144,0,0,816,833,1,0,0,0,817,818,5,126,0,0,818,819,3,104, + 52,0,819,820,5,144,0,0,820,833,1,0,0,0,821,823,5,125,0,0,822,824,3,104, + 52,0,823,822,1,0,0,0,823,824,1,0,0,0,824,825,1,0,0,0,825,833,5,143,0, + 0,826,828,5,124,0,0,827,829,3,30,15,0,828,827,1,0,0,0,828,829,1,0,0,0, + 829,830,1,0,0,0,830,833,5,142,0,0,831,833,3,122,61,0,832,683,1,0,0,0, + 832,703,1,0,0,0,832,710,1,0,0,0,832,712,1,0,0,0,832,716,1,0,0,0,832,727, + 1,0,0,0,832,729,1,0,0,0,832,737,1,0,0,0,832,759,1,0,0,0,832,779,1,0,0, + 0,832,796,1,0,0,0,832,797,1,0,0,0,832,798,1,0,0,0,832,799,1,0,0,0,832, + 801,1,0,0,0,832,806,1,0,0,0,832,809,1,0,0,0,832,813,1,0,0,0,832,817,1, + 0,0,0,832,821,1,0,0,0,832,826,1,0,0,0,832,831,1,0,0,0,833,927,1,0,0,0, + 834,838,10,18,0,0,835,839,5,108,0,0,836,839,5,146,0,0,837,839,5,133,0, + 0,838,835,1,0,0,0,838,836,1,0,0,0,838,837,1,0,0,0,839,840,1,0,0,0,840, + 926,3,106,53,19,841,845,10,17,0,0,842,846,5,134,0,0,843,846,5,114,0,0, + 844,846,5,113,0,0,845,842,1,0,0,0,845,843,1,0,0,0,845,844,1,0,0,0,846, + 847,1,0,0,0,847,926,3,106,53,18,848,873,10,16,0,0,849,874,5,117,0,0,850, + 874,5,118,0,0,851,874,5,129,0,0,852,874,5,127,0,0,853,874,5,128,0,0,854, + 874,5,119,0,0,855,874,5,120,0,0,856,858,5,56,0,0,857,856,1,0,0,0,857, + 858,1,0,0,0,858,859,1,0,0,0,859,861,5,40,0,0,860,862,5,14,0,0,861,860, + 1,0,0,0,861,862,1,0,0,0,862,874,1,0,0,0,863,865,5,56,0,0,864,863,1,0, + 0,0,864,865,1,0,0,0,865,866,1,0,0,0,866,874,7,10,0,0,867,874,5,140,0, + 0,868,874,5,141,0,0,869,874,5,131,0,0,870,874,5,122,0,0,871,874,5,123, + 0,0,872,874,5,130,0,0,873,849,1,0,0,0,873,850,1,0,0,0,873,851,1,0,0,0, + 873,852,1,0,0,0,873,853,1,0,0,0,873,854,1,0,0,0,873,855,1,0,0,0,873,857, + 1,0,0,0,873,864,1,0,0,0,873,867,1,0,0,0,873,868,1,0,0,0,873,869,1,0,0, + 0,873,870,1,0,0,0,873,871,1,0,0,0,873,872,1,0,0,0,874,875,1,0,0,0,875, + 926,3,106,53,17,876,877,10,14,0,0,877,878,5,132,0,0,878,926,3,106,53, + 15,879,880,10,12,0,0,880,881,5,2,0,0,881,926,3,106,53,13,882,883,10,11, + 0,0,883,884,5,61,0,0,884,926,3,106,53,12,885,887,10,10,0,0,886,888,5, + 56,0,0,887,886,1,0,0,0,887,888,1,0,0,0,888,889,1,0,0,0,889,890,5,9,0, + 0,890,891,3,106,53,0,891,892,5,2,0,0,892,893,3,106,53,11,893,926,1,0, + 0,0,894,895,10,9,0,0,895,896,5,135,0,0,896,897,3,106,53,0,897,898,5,111, + 0,0,898,899,3,106,53,9,899,926,1,0,0,0,900,901,10,22,0,0,901,902,5,125, + 0,0,902,903,3,106,53,0,903,904,5,143,0,0,904,926,1,0,0,0,905,906,10,21, + 0,0,906,907,5,116,0,0,907,926,5,104,0,0,908,909,10,20,0,0,909,910,5,116, + 0,0,910,926,3,150,75,0,911,912,10,15,0,0,912,914,5,44,0,0,913,915,5,56, + 0,0,914,913,1,0,0,0,914,915,1,0,0,0,915,916,1,0,0,0,916,926,5,57,0,0, + 917,923,10,8,0,0,918,924,3,148,74,0,919,920,5,6,0,0,920,924,3,150,75, + 0,921,922,5,6,0,0,922,924,5,106,0,0,923,918,1,0,0,0,923,919,1,0,0,0,923, + 921,1,0,0,0,924,926,1,0,0,0,925,834,1,0,0,0,925,841,1,0,0,0,925,848,1, + 0,0,0,925,876,1,0,0,0,925,879,1,0,0,0,925,882,1,0,0,0,925,885,1,0,0,0, + 925,894,1,0,0,0,925,900,1,0,0,0,925,905,1,0,0,0,925,908,1,0,0,0,925,911, + 1,0,0,0,925,917,1,0,0,0,926,929,1,0,0,0,927,925,1,0,0,0,927,928,1,0,0, + 0,928,107,1,0,0,0,929,927,1,0,0,0,930,935,3,110,55,0,931,932,5,112,0, + 0,932,934,3,110,55,0,933,931,1,0,0,0,934,937,1,0,0,0,935,933,1,0,0,0, + 935,936,1,0,0,0,936,109,1,0,0,0,937,935,1,0,0,0,938,941,3,112,56,0,939, + 941,3,106,53,0,940,938,1,0,0,0,940,939,1,0,0,0,941,111,1,0,0,0,942,943, + 5,126,0,0,943,948,3,150,75,0,944,945,5,112,0,0,945,947,3,150,75,0,946, + 944,1,0,0,0,947,950,1,0,0,0,948,946,1,0,0,0,948,949,1,0,0,0,949,951,1, + 0,0,0,950,948,1,0,0,0,951,952,5,144,0,0,952,962,1,0,0,0,953,958,3,150, + 75,0,954,955,5,112,0,0,955,957,3,150,75,0,956,954,1,0,0,0,957,960,1,0, + 0,0,958,956,1,0,0,0,958,959,1,0,0,0,959,962,1,0,0,0,960,958,1,0,0,0,961, + 942,1,0,0,0,961,953,1,0,0,0,962,963,1,0,0,0,963,964,5,107,0,0,964,965, + 3,106,53,0,965,113,1,0,0,0,966,967,5,128,0,0,967,971,3,150,75,0,968,970, + 3,116,58,0,969,968,1,0,0,0,970,973,1,0,0,0,971,969,1,0,0,0,971,972,1, + 0,0,0,972,974,1,0,0,0,973,971,1,0,0,0,974,975,5,146,0,0,975,976,5,120, + 0,0,976,995,1,0,0,0,977,978,5,128,0,0,978,982,3,150,75,0,979,981,3,116, + 58,0,980,979,1,0,0,0,981,984,1,0,0,0,982,980,1,0,0,0,982,983,1,0,0,0, + 983,985,1,0,0,0,984,982,1,0,0,0,985,987,5,120,0,0,986,988,3,114,57,0, + 987,986,1,0,0,0,987,988,1,0,0,0,988,989,1,0,0,0,989,990,5,128,0,0,990, + 991,5,146,0,0,991,992,3,150,75,0,992,993,5,120,0,0,993,995,1,0,0,0,994, + 966,1,0,0,0,994,977,1,0,0,0,995,115,1,0,0,0,996,997,3,150,75,0,997,998, + 5,118,0,0,998,999,3,156,78,0,999,1008,1,0,0,0,1000,1001,3,150,75,0,1001, + 1002,5,118,0,0,1002,1003,5,124,0,0,1003,1004,3,106,53,0,1004,1005,5,142, + 0,0,1005,1008,1,0,0,0,1006,1008,3,150,75,0,1007,996,1,0,0,0,1007,1000, + 1,0,0,0,1007,1006,1,0,0,0,1008,117,1,0,0,0,1009,1014,3,120,60,0,1010, + 1011,5,112,0,0,1011,1013,3,120,60,0,1012,1010,1,0,0,0,1013,1016,1,0,0, + 0,1014,1012,1,0,0,0,1014,1015,1,0,0,0,1015,119,1,0,0,0,1016,1014,1,0, + 0,0,1017,1018,3,150,75,0,1018,1019,5,6,0,0,1019,1020,5,126,0,0,1020,1021, + 3,34,17,0,1021,1022,5,144,0,0,1022,1028,1,0,0,0,1023,1024,3,106,53,0, + 1024,1025,5,6,0,0,1025,1026,3,150,75,0,1026,1028,1,0,0,0,1027,1017,1, + 0,0,0,1027,1023,1,0,0,0,1028,121,1,0,0,0,1029,1037,3,154,77,0,1030,1031, + 3,130,65,0,1031,1032,5,116,0,0,1032,1034,1,0,0,0,1033,1030,1,0,0,0,1033, + 1034,1,0,0,0,1034,1035,1,0,0,0,1035,1037,3,124,62,0,1036,1029,1,0,0,0, + 1036,1033,1,0,0,0,1037,123,1,0,0,0,1038,1043,3,150,75,0,1039,1040,5,116, + 0,0,1040,1042,3,150,75,0,1041,1039,1,0,0,0,1042,1045,1,0,0,0,1043,1041, + 1,0,0,0,1043,1044,1,0,0,0,1044,125,1,0,0,0,1045,1043,1,0,0,0,1046,1047, + 6,63,-1,0,1047,1056,3,130,65,0,1048,1056,3,128,64,0,1049,1050,5,126,0, + 0,1050,1051,3,34,17,0,1051,1052,5,144,0,0,1052,1056,1,0,0,0,1053,1056, + 3,114,57,0,1054,1056,3,154,77,0,1055,1046,1,0,0,0,1055,1048,1,0,0,0,1055, + 1049,1,0,0,0,1055,1053,1,0,0,0,1055,1054,1,0,0,0,1056,1065,1,0,0,0,1057, + 1061,10,3,0,0,1058,1062,3,148,74,0,1059,1060,5,6,0,0,1060,1062,3,150, + 75,0,1061,1058,1,0,0,0,1061,1059,1,0,0,0,1062,1064,1,0,0,0,1063,1057, + 1,0,0,0,1064,1067,1,0,0,0,1065,1063,1,0,0,0,1065,1066,1,0,0,0,1066,127, + 1,0,0,0,1067,1065,1,0,0,0,1068,1069,3,150,75,0,1069,1071,5,126,0,0,1070, + 1072,3,132,66,0,1071,1070,1,0,0,0,1071,1072,1,0,0,0,1072,1073,1,0,0,0, + 1073,1074,5,144,0,0,1074,129,1,0,0,0,1075,1076,3,134,67,0,1076,1077,5, + 116,0,0,1077,1079,1,0,0,0,1078,1075,1,0,0,0,1078,1079,1,0,0,0,1079,1080, + 1,0,0,0,1080,1081,3,150,75,0,1081,131,1,0,0,0,1082,1087,3,106,53,0,1083, + 1084,5,112,0,0,1084,1086,3,106,53,0,1085,1083,1,0,0,0,1086,1089,1,0,0, + 0,1087,1085,1,0,0,0,1087,1088,1,0,0,0,1088,133,1,0,0,0,1089,1087,1,0, + 0,0,1090,1091,3,150,75,0,1091,135,1,0,0,0,1092,1101,5,102,0,0,1093,1094, + 5,116,0,0,1094,1101,7,11,0,0,1095,1096,5,104,0,0,1096,1098,5,116,0,0, + 1097,1099,7,11,0,0,1098,1097,1,0,0,0,1098,1099,1,0,0,0,1099,1101,1,0, + 0,0,1100,1092,1,0,0,0,1100,1093,1,0,0,0,1100,1095,1,0,0,0,1101,137,1, + 0,0,0,1102,1104,7,12,0,0,1103,1102,1,0,0,0,1103,1104,1,0,0,0,1104,1111, + 1,0,0,0,1105,1112,3,136,68,0,1106,1112,5,103,0,0,1107,1112,5,104,0,0, + 1108,1112,5,105,0,0,1109,1112,5,41,0,0,1110,1112,5,55,0,0,1111,1105,1, + 0,0,0,1111,1106,1,0,0,0,1111,1107,1,0,0,0,1111,1108,1,0,0,0,1111,1109, + 1,0,0,0,1111,1110,1,0,0,0,1112,139,1,0,0,0,1113,1117,3,138,69,0,1114, + 1117,5,106,0,0,1115,1117,5,57,0,0,1116,1113,1,0,0,0,1116,1114,1,0,0,0, + 1116,1115,1,0,0,0,1117,141,1,0,0,0,1118,1119,7,13,0,0,1119,143,1,0,0, + 0,1120,1121,7,14,0,0,1121,145,1,0,0,0,1122,1123,7,15,0,0,1123,147,1,0, + 0,0,1124,1127,5,101,0,0,1125,1127,3,146,73,0,1126,1124,1,0,0,0,1126,1125, + 1,0,0,0,1127,149,1,0,0,0,1128,1132,5,101,0,0,1129,1132,3,142,71,0,1130, + 1132,3,144,72,0,1131,1128,1,0,0,0,1131,1129,1,0,0,0,1131,1130,1,0,0,0, + 1132,151,1,0,0,0,1133,1134,3,156,78,0,1134,1135,5,118,0,0,1135,1136,3, + 138,69,0,1136,153,1,0,0,0,1137,1138,5,124,0,0,1138,1139,3,150,75,0,1139, + 1140,5,142,0,0,1140,155,1,0,0,0,1141,1144,5,106,0,0,1142,1144,3,158,79, + 0,1143,1141,1,0,0,0,1143,1142,1,0,0,0,1144,157,1,0,0,0,1145,1149,5,137, + 0,0,1146,1148,3,160,80,0,1147,1146,1,0,0,0,1148,1151,1,0,0,0,1149,1147, + 1,0,0,0,1149,1150,1,0,0,0,1150,1152,1,0,0,0,1151,1149,1,0,0,0,1152,1153, + 5,139,0,0,1153,159,1,0,0,0,1154,1155,5,152,0,0,1155,1156,3,106,53,0,1156, + 1157,5,142,0,0,1157,1160,1,0,0,0,1158,1160,5,151,0,0,1159,1154,1,0,0, + 0,1159,1158,1,0,0,0,1160,161,1,0,0,0,1161,1165,5,138,0,0,1162,1164,3, + 164,82,0,1163,1162,1,0,0,0,1164,1167,1,0,0,0,1165,1163,1,0,0,0,1165,1166, + 1,0,0,0,1166,1168,1,0,0,0,1167,1165,1,0,0,0,1168,1169,5,0,0,1,1169,163, + 1,0,0,0,1170,1171,5,154,0,0,1171,1172,3,106,53,0,1172,1173,5,142,0,0, + 1173,1176,1,0,0,0,1174,1176,5,153,0,0,1175,1170,1,0,0,0,1175,1174,1,0, + 0,0,1176,165,1,0,0,0,141,169,176,185,200,212,224,240,251,265,271,281, 290,293,297,300,304,307,310,313,316,320,324,327,330,333,337,340,349,355, 376,393,410,416,422,433,435,446,449,455,463,469,471,475,480,483,486,490, 494,497,499,502,506,510,513,515,517,522,533,539,546,551,555,559,565,567, - 574,582,585,588,607,621,637,649,661,669,673,680,686,695,699,723,740,752, - 762,765,769,772,786,803,808,812,818,825,837,841,844,853,867,894,903,905, - 907,915,920,928,938,941,951,962,967,974,987,994,1007,1013,1016,1023,1035, - 1041,1045,1051,1058,1067,1078,1080,1083,1091,1096,1106,1111,1123,1129, - 1139,1145,1155 + 574,582,585,588,607,621,637,649,661,669,673,680,686,695,699,723,740,746, + 749,752,762,768,771,774,782,785,789,792,806,823,828,832,838,845,857,861, + 864,873,887,914,923,925,927,935,940,948,958,961,971,982,987,994,1007, + 1014,1027,1033,1036,1043,1055,1061,1065,1071,1078,1087,1098,1100,1103, + 1111,1116,1126,1131,1143,1149,1159,1165,1175 }; staticData->serializedATN = antlr4::atn::SerializedATNView(serializedATNSegment, sizeof(serializedATNSegment) / sizeof(serializedATNSegment[0])); @@ -6335,18 +6344,34 @@ tree::TerminalNode* HogQLParser::ColumnExprWinFunctionTargetContext::OVER() { return getToken(HogQLParser::OVER, 0); } -tree::TerminalNode* HogQLParser::ColumnExprWinFunctionTargetContext::LPAREN() { - return getToken(HogQLParser::LPAREN, 0); +std::vector HogQLParser::ColumnExprWinFunctionTargetContext::LPAREN() { + return getTokens(HogQLParser::LPAREN); } -tree::TerminalNode* HogQLParser::ColumnExprWinFunctionTargetContext::RPAREN() { - return getToken(HogQLParser::RPAREN, 0); +tree::TerminalNode* HogQLParser::ColumnExprWinFunctionTargetContext::LPAREN(size_t i) { + return getToken(HogQLParser::LPAREN, i); +} + +std::vector HogQLParser::ColumnExprWinFunctionTargetContext::RPAREN() { + return getTokens(HogQLParser::RPAREN); +} + +tree::TerminalNode* HogQLParser::ColumnExprWinFunctionTargetContext::RPAREN(size_t i) { + return getToken(HogQLParser::RPAREN, i); } HogQLParser::ColumnExprListContext* HogQLParser::ColumnExprWinFunctionTargetContext::columnExprList() { return getRuleContext(0); } +tree::TerminalNode* HogQLParser::ColumnExprWinFunctionTargetContext::DISTINCT() { + return getToken(HogQLParser::DISTINCT, 0); +} + +HogQLParser::ColumnArgListContext* HogQLParser::ColumnExprWinFunctionTargetContext::columnArgList() { + return getRuleContext(0); +} + HogQLParser::ColumnExprWinFunctionTargetContext::ColumnExprWinFunctionTargetContext(ColumnExprContext *ctx) { copyFrom(ctx); } @@ -6767,6 +6792,14 @@ HogQLParser::ColumnExprListContext* HogQLParser::ColumnExprWinFunctionContext::c return getRuleContext(0); } +tree::TerminalNode* HogQLParser::ColumnExprWinFunctionContext::DISTINCT() { + return getToken(HogQLParser::DISTINCT, 0); +} + +HogQLParser::ColumnArgListContext* HogQLParser::ColumnExprWinFunctionContext::columnArgList() { + return getRuleContext(0); +} + HogQLParser::ColumnExprWinFunctionContext::ColumnExprWinFunctionContext(ColumnExprContext *ctx) { copyFrom(ctx); } @@ -6883,9 +6916,9 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { try { size_t alt; enterOuterAlt(_localctx, 1); - setState(812); + setState(832); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 90, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 96, _ctx)) { case 1: { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; @@ -7072,13 +7105,47 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { } setState(742); match(HogQLParser::RPAREN); - setState(744); + setState(752); + _errHandler->sync(this); + + _la = _input->LA(1); + if (_la == HogQLParser::LPAREN) { + setState(744); + match(HogQLParser::LPAREN); + setState(746); + _errHandler->sync(this); + + switch (getInterpreter()->adaptivePredict(_input, 82, _ctx)) { + case 1: { + setState(745); + match(HogQLParser::DISTINCT); + break; + } + + default: + break; + } + setState(749); + _errHandler->sync(this); + + _la = _input->LA(1); + if ((((_la & ~ 0x3fULL) == 0) && + ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && + ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && + ((1ULL << (_la - 128)) & 577) != 0)) { + setState(748); + columnArgList(); + } + setState(751); + match(HogQLParser::RPAREN); + } + setState(754); match(HogQLParser::OVER); - setState(745); + setState(755); match(HogQLParser::LPAREN); - setState(746); + setState(756); windowExpr(); - setState(747); + setState(757); match(HogQLParser::RPAREN); break; } @@ -7087,12 +7154,12 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(749); + setState(759); identifier(); - setState(750); + setState(760); match(HogQLParser::LPAREN); - setState(752); + setState(762); _errHandler->sync(this); _la = _input->LA(1); @@ -7100,14 +7167,48 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 128)) & 577) != 0)) { - setState(751); + setState(761); columnExprList(); } - setState(754); + setState(764); match(HogQLParser::RPAREN); - setState(756); + setState(774); + _errHandler->sync(this); + + _la = _input->LA(1); + if (_la == HogQLParser::LPAREN) { + setState(766); + match(HogQLParser::LPAREN); + setState(768); + _errHandler->sync(this); + + switch (getInterpreter()->adaptivePredict(_input, 86, _ctx)) { + case 1: { + setState(767); + match(HogQLParser::DISTINCT); + break; + } + + default: + break; + } + setState(771); + _errHandler->sync(this); + + _la = _input->LA(1); + if ((((_la & ~ 0x3fULL) == 0) && + ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && + ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && + ((1ULL << (_la - 128)) & 577) != 0)) { + setState(770); + columnArgList(); + } + setState(773); + match(HogQLParser::RPAREN); + } + setState(776); match(HogQLParser::OVER); - setState(757); + setState(777); identifier(); break; } @@ -7116,16 +7217,16 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(759); + setState(779); identifier(); - setState(765); + setState(785); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 84, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 90, _ctx)) { case 1: { - setState(760); + setState(780); match(HogQLParser::LPAREN); - setState(762); + setState(782); _errHandler->sync(this); _la = _input->LA(1); @@ -7133,10 +7234,10 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 128)) & 577) != 0)) { - setState(761); + setState(781); columnExprList(); } - setState(764); + setState(784); match(HogQLParser::RPAREN); break; } @@ -7144,14 +7245,14 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { default: break; } - setState(767); + setState(787); match(HogQLParser::LPAREN); - setState(769); + setState(789); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 85, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 91, _ctx)) { case 1: { - setState(768); + setState(788); match(HogQLParser::DISTINCT); break; } @@ -7159,7 +7260,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { default: break; } - setState(772); + setState(792); _errHandler->sync(this); _la = _input->LA(1); @@ -7167,10 +7268,10 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 128)) & 577) != 0)) { - setState(771); + setState(791); columnArgList(); } - setState(774); + setState(794); match(HogQLParser::RPAREN); break; } @@ -7179,7 +7280,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(776); + setState(796); hogqlxTagElement(); break; } @@ -7188,7 +7289,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(777); + setState(797); templateString(); break; } @@ -7197,7 +7298,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(778); + setState(798); literal(); break; } @@ -7206,9 +7307,9 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(779); + setState(799); match(HogQLParser::DASH); - setState(780); + setState(800); columnExpr(19); break; } @@ -7217,9 +7318,9 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(781); + setState(801); match(HogQLParser::NOT); - setState(782); + setState(802); columnExpr(13); break; } @@ -7228,19 +7329,19 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(786); + setState(806); _errHandler->sync(this); _la = _input->LA(1); if ((((_la & ~ 0x3fULL) == 0) && ((1ULL << _la) & -181272084561788930) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 201863462911) != 0)) { - setState(783); + setState(803); tableIdentifier(); - setState(784); + setState(804); match(HogQLParser::DOT); } - setState(788); + setState(808); match(HogQLParser::ASTERISK); break; } @@ -7249,11 +7350,11 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(789); + setState(809); match(HogQLParser::LPAREN); - setState(790); + setState(810); selectUnionStmt(); - setState(791); + setState(811); match(HogQLParser::RPAREN); break; } @@ -7262,11 +7363,11 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(793); + setState(813); match(HogQLParser::LPAREN); - setState(794); + setState(814); columnExpr(0); - setState(795); + setState(815); match(HogQLParser::RPAREN); break; } @@ -7275,11 +7376,11 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(797); + setState(817); match(HogQLParser::LPAREN); - setState(798); + setState(818); columnExprList(); - setState(799); + setState(819); match(HogQLParser::RPAREN); break; } @@ -7288,9 +7389,9 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(801); + setState(821); match(HogQLParser::LBRACKET); - setState(803); + setState(823); _errHandler->sync(this); _la = _input->LA(1); @@ -7298,10 +7399,10 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 128)) & 577) != 0)) { - setState(802); + setState(822); columnExprList(); } - setState(805); + setState(825); match(HogQLParser::RBRACKET); break; } @@ -7310,9 +7411,9 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(806); + setState(826); match(HogQLParser::LBRACE); - setState(808); + setState(828); _errHandler->sync(this); _la = _input->LA(1); @@ -7320,10 +7421,10 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 128)) & 577) != 0)) { - setState(807); + setState(827); kvPairList(); } - setState(810); + setState(830); match(HogQLParser::RBRACE); break; } @@ -7332,7 +7433,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(811); + setState(831); columnIdentifier(); break; } @@ -7341,42 +7442,42 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { break; } _ctx->stop = _input->LT(-1); - setState(907); + setState(927); _errHandler->sync(this); - alt = getInterpreter()->adaptivePredict(_input, 101, _ctx); + alt = getInterpreter()->adaptivePredict(_input, 107, _ctx); while (alt != 2 && alt != atn::ATN::INVALID_ALT_NUMBER) { if (alt == 1) { if (!_parseListeners.empty()) triggerExitRuleEvent(); previousContext = _localctx; - setState(905); + setState(925); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 100, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 106, _ctx)) { case 1: { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; newContext->left = previousContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(814); + setState(834); if (!(precpred(_ctx, 18))) throw FailedPredicateException(this, "precpred(_ctx, 18)"); - setState(818); + setState(838); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::ASTERISK: { - setState(815); + setState(835); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::ASTERISK); break; } case HogQLParser::SLASH: { - setState(816); + setState(836); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::SLASH); break; } case HogQLParser::PERCENT: { - setState(817); + setState(837); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::PERCENT); break; } @@ -7384,7 +7485,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { default: throw NoViableAltException(this); } - setState(820); + setState(840); antlrcpp::downCast(_localctx)->right = columnExpr(19); break; } @@ -7394,26 +7495,26 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = newContext; newContext->left = previousContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(821); + setState(841); if (!(precpred(_ctx, 17))) throw FailedPredicateException(this, "precpred(_ctx, 17)"); - setState(825); + setState(845); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::PLUS: { - setState(822); + setState(842); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::PLUS); break; } case HogQLParser::DASH: { - setState(823); + setState(843); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::DASH); break; } case HogQLParser::CONCAT: { - setState(824); + setState(844); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::CONCAT); break; } @@ -7421,7 +7522,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { default: throw NoViableAltException(this); } - setState(827); + setState(847); antlrcpp::downCast(_localctx)->right = columnExpr(18); break; } @@ -7431,71 +7532,71 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { _localctx = newContext; newContext->left = previousContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(828); + setState(848); if (!(precpred(_ctx, 16))) throw FailedPredicateException(this, "precpred(_ctx, 16)"); - setState(853); + setState(873); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 96, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 102, _ctx)) { case 1: { - setState(829); + setState(849); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::EQ_DOUBLE); break; } case 2: { - setState(830); + setState(850); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::EQ_SINGLE); break; } case 3: { - setState(831); + setState(851); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::NOT_EQ); break; } case 4: { - setState(832); + setState(852); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::LT_EQ); break; } case 5: { - setState(833); + setState(853); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::LT); break; } case 6: { - setState(834); + setState(854); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::GT_EQ); break; } case 7: { - setState(835); + setState(855); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::GT); break; } case 8: { - setState(837); + setState(857); _errHandler->sync(this); _la = _input->LA(1); if (_la == HogQLParser::NOT) { - setState(836); + setState(856); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::NOT); } - setState(839); + setState(859); match(HogQLParser::IN); - setState(841); + setState(861); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 94, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 100, _ctx)) { case 1: { - setState(840); + setState(860); match(HogQLParser::COHORT); break; } @@ -7507,15 +7608,15 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { } case 9: { - setState(844); + setState(864); _errHandler->sync(this); _la = _input->LA(1); if (_la == HogQLParser::NOT) { - setState(843); + setState(863); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::NOT); } - setState(846); + setState(866); _la = _input->LA(1); if (!(_la == HogQLParser::ILIKE @@ -7530,37 +7631,37 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { } case 10: { - setState(847); + setState(867); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::REGEX_SINGLE); break; } case 11: { - setState(848); + setState(868); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::REGEX_DOUBLE); break; } case 12: { - setState(849); + setState(869); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::NOT_REGEX); break; } case 13: { - setState(850); + setState(870); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::IREGEX_SINGLE); break; } case 14: { - setState(851); + setState(871); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::IREGEX_DOUBLE); break; } case 15: { - setState(852); + setState(872); antlrcpp::downCast(_localctx)->operator_ = match(HogQLParser::NOT_IREGEX); break; } @@ -7568,7 +7669,7 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { default: break; } - setState(855); + setState(875); antlrcpp::downCast(_localctx)->right = columnExpr(17); break; } @@ -7577,12 +7678,12 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(856); + setState(876); if (!(precpred(_ctx, 14))) throw FailedPredicateException(this, "precpred(_ctx, 14)"); - setState(857); + setState(877); match(HogQLParser::NULLISH); - setState(858); + setState(878); columnExpr(15); break; } @@ -7591,12 +7692,12 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(859); + setState(879); if (!(precpred(_ctx, 12))) throw FailedPredicateException(this, "precpred(_ctx, 12)"); - setState(860); + setState(880); match(HogQLParser::AND); - setState(861); + setState(881); columnExpr(13); break; } @@ -7605,12 +7706,12 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(862); + setState(882); if (!(precpred(_ctx, 11))) throw FailedPredicateException(this, "precpred(_ctx, 11)"); - setState(863); + setState(883); match(HogQLParser::OR); - setState(864); + setState(884); columnExpr(12); break; } @@ -7619,24 +7720,24 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(865); + setState(885); if (!(precpred(_ctx, 10))) throw FailedPredicateException(this, "precpred(_ctx, 10)"); - setState(867); + setState(887); _errHandler->sync(this); _la = _input->LA(1); if (_la == HogQLParser::NOT) { - setState(866); + setState(886); match(HogQLParser::NOT); } - setState(869); + setState(889); match(HogQLParser::BETWEEN); - setState(870); + setState(890); columnExpr(0); - setState(871); + setState(891); match(HogQLParser::AND); - setState(872); + setState(892); columnExpr(11); break; } @@ -7645,16 +7746,16 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(874); + setState(894); if (!(precpred(_ctx, 9))) throw FailedPredicateException(this, "precpred(_ctx, 9)"); - setState(875); + setState(895); match(HogQLParser::QUERY); - setState(876); + setState(896); columnExpr(0); - setState(877); + setState(897); match(HogQLParser::COLON); - setState(878); + setState(898); columnExpr(9); break; } @@ -7663,14 +7764,14 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(880); + setState(900); if (!(precpred(_ctx, 22))) throw FailedPredicateException(this, "precpred(_ctx, 22)"); - setState(881); + setState(901); match(HogQLParser::LBRACKET); - setState(882); + setState(902); columnExpr(0); - setState(883); + setState(903); match(HogQLParser::RBRACKET); break; } @@ -7679,12 +7780,12 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(885); + setState(905); if (!(precpred(_ctx, 21))) throw FailedPredicateException(this, "precpred(_ctx, 21)"); - setState(886); + setState(906); match(HogQLParser::DOT); - setState(887); + setState(907); match(HogQLParser::DECIMAL_LITERAL); break; } @@ -7693,12 +7794,12 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(888); + setState(908); if (!(precpred(_ctx, 20))) throw FailedPredicateException(this, "precpred(_ctx, 20)"); - setState(889); + setState(909); match(HogQLParser::DOT); - setState(890); + setState(910); identifier(); break; } @@ -7707,20 +7808,20 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(891); + setState(911); if (!(precpred(_ctx, 15))) throw FailedPredicateException(this, "precpred(_ctx, 15)"); - setState(892); + setState(912); match(HogQLParser::IS); - setState(894); + setState(914); _errHandler->sync(this); _la = _input->LA(1); if (_la == HogQLParser::NOT) { - setState(893); + setState(913); match(HogQLParser::NOT); } - setState(896); + setState(916); match(HogQLParser::NULL_SQL); break; } @@ -7729,30 +7830,30 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleColumnExpr); - setState(897); + setState(917); if (!(precpred(_ctx, 8))) throw FailedPredicateException(this, "precpred(_ctx, 8)"); - setState(903); + setState(923); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 99, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 105, _ctx)) { case 1: { - setState(898); + setState(918); alias(); break; } case 2: { - setState(899); + setState(919); match(HogQLParser::AS); - setState(900); + setState(920); identifier(); break; } case 3: { - setState(901); + setState(921); match(HogQLParser::AS); - setState(902); + setState(922); match(HogQLParser::STRING_LITERAL); break; } @@ -7767,9 +7868,9 @@ HogQLParser::ColumnExprContext* HogQLParser::columnExpr(int precedence) { break; } } - setState(909); + setState(929); _errHandler->sync(this); - alt = getInterpreter()->adaptivePredict(_input, 101, _ctx); + alt = getInterpreter()->adaptivePredict(_input, 107, _ctx); } } catch (RecognitionException &e) { @@ -7829,17 +7930,17 @@ HogQLParser::ColumnArgListContext* HogQLParser::columnArgList() { }); try { enterOuterAlt(_localctx, 1); - setState(910); + setState(930); columnArgExpr(); - setState(915); + setState(935); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::COMMA) { - setState(911); + setState(931); match(HogQLParser::COMMA); - setState(912); + setState(932); columnArgExpr(); - setState(917); + setState(937); _errHandler->sync(this); _la = _input->LA(1); } @@ -7893,19 +7994,19 @@ HogQLParser::ColumnArgExprContext* HogQLParser::columnArgExpr() { exitRule(); }); try { - setState(920); + setState(940); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 103, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 109, _ctx)) { case 1: { enterOuterAlt(_localctx, 1); - setState(918); + setState(938); columnLambdaExpr(); break; } case 2: { enterOuterAlt(_localctx, 2); - setState(919); + setState(939); columnExpr(0); break; } @@ -7989,27 +8090,27 @@ HogQLParser::ColumnLambdaExprContext* HogQLParser::columnLambdaExpr() { }); try { enterOuterAlt(_localctx, 1); - setState(941); + setState(961); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::LPAREN: { - setState(922); + setState(942); match(HogQLParser::LPAREN); - setState(923); + setState(943); identifier(); - setState(928); + setState(948); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::COMMA) { - setState(924); + setState(944); match(HogQLParser::COMMA); - setState(925); + setState(945); identifier(); - setState(930); + setState(950); _errHandler->sync(this); _la = _input->LA(1); } - setState(931); + setState(951); match(HogQLParser::RPAREN); break; } @@ -8108,17 +8209,17 @@ HogQLParser::ColumnLambdaExprContext* HogQLParser::columnLambdaExpr() { case HogQLParser::WITH: case HogQLParser::YEAR: case HogQLParser::IDENTIFIER: { - setState(933); + setState(953); identifier(); - setState(938); + setState(958); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::COMMA) { - setState(934); + setState(954); match(HogQLParser::COMMA); - setState(935); + setState(955); identifier(); - setState(940); + setState(960); _errHandler->sync(this); _la = _input->LA(1); } @@ -8128,9 +8229,9 @@ HogQLParser::ColumnLambdaExprContext* HogQLParser::columnLambdaExpr() { default: throw NoViableAltException(this); } - setState(943); + setState(963); match(HogQLParser::ARROW); - setState(944); + setState(964); columnExpr(0); } @@ -8257,31 +8358,31 @@ HogQLParser::HogqlxTagElementContext* HogQLParser::hogqlxTagElement() { exitRule(); }); try { - setState(974); + setState(994); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 110, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 116, _ctx)) { case 1: { _localctx = _tracker.createInstance(_localctx); enterOuterAlt(_localctx, 1); - setState(946); + setState(966); match(HogQLParser::LT); - setState(947); + setState(967); identifier(); - setState(951); + setState(971); _errHandler->sync(this); _la = _input->LA(1); while ((((_la & ~ 0x3fULL) == 0) && ((1ULL << _la) & -181272084561788930) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 201863462911) != 0)) { - setState(948); + setState(968); hogqlxTagAttribute(); - setState(953); + setState(973); _errHandler->sync(this); _la = _input->LA(1); } - setState(954); + setState(974); match(HogQLParser::SLASH); - setState(955); + setState(975); match(HogQLParser::GT); break; } @@ -8289,30 +8390,30 @@ HogQLParser::HogqlxTagElementContext* HogQLParser::hogqlxTagElement() { case 2: { _localctx = _tracker.createInstance(_localctx); enterOuterAlt(_localctx, 2); - setState(957); + setState(977); match(HogQLParser::LT); - setState(958); + setState(978); identifier(); - setState(962); + setState(982); _errHandler->sync(this); _la = _input->LA(1); while ((((_la & ~ 0x3fULL) == 0) && ((1ULL << _la) & -181272084561788930) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 201863462911) != 0)) { - setState(959); + setState(979); hogqlxTagAttribute(); - setState(964); + setState(984); _errHandler->sync(this); _la = _input->LA(1); } - setState(965); + setState(985); match(HogQLParser::GT); - setState(967); + setState(987); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 109, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 115, _ctx)) { case 1: { - setState(966); + setState(986); hogqlxTagElement(); break; } @@ -8320,13 +8421,13 @@ HogQLParser::HogqlxTagElementContext* HogQLParser::hogqlxTagElement() { default: break; } - setState(969); + setState(989); match(HogQLParser::LT); - setState(970); + setState(990); match(HogQLParser::SLASH); - setState(971); + setState(991); identifier(); - setState(972); + setState(992); match(HogQLParser::GT); break; } @@ -8400,38 +8501,38 @@ HogQLParser::HogqlxTagAttributeContext* HogQLParser::hogqlxTagAttribute() { exitRule(); }); try { - setState(987); + setState(1007); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 111, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 117, _ctx)) { case 1: { enterOuterAlt(_localctx, 1); - setState(976); + setState(996); identifier(); - setState(977); + setState(997); match(HogQLParser::EQ_SINGLE); - setState(978); + setState(998); string(); break; } case 2: { enterOuterAlt(_localctx, 2); - setState(980); + setState(1000); identifier(); - setState(981); + setState(1001); match(HogQLParser::EQ_SINGLE); - setState(982); + setState(1002); match(HogQLParser::LBRACE); - setState(983); + setState(1003); columnExpr(0); - setState(984); + setState(1004); match(HogQLParser::RBRACE); break; } case 3: { enterOuterAlt(_localctx, 3); - setState(986); + setState(1006); identifier(); break; } @@ -8499,17 +8600,17 @@ HogQLParser::WithExprListContext* HogQLParser::withExprList() { }); try { enterOuterAlt(_localctx, 1); - setState(989); + setState(1009); withExpr(); - setState(994); + setState(1014); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::COMMA) { - setState(990); + setState(1010); match(HogQLParser::COMMA); - setState(991); + setState(1011); withExpr(); - setState(996); + setState(1016); _errHandler->sync(this); _la = _input->LA(1); } @@ -8605,21 +8706,21 @@ HogQLParser::WithExprContext* HogQLParser::withExpr() { exitRule(); }); try { - setState(1007); + setState(1027); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 113, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 119, _ctx)) { case 1: { _localctx = _tracker.createInstance(_localctx); enterOuterAlt(_localctx, 1); - setState(997); + setState(1017); identifier(); - setState(998); + setState(1018); match(HogQLParser::AS); - setState(999); + setState(1019); match(HogQLParser::LPAREN); - setState(1000); + setState(1020); selectUnionStmt(); - setState(1001); + setState(1021); match(HogQLParser::RPAREN); break; } @@ -8627,11 +8728,11 @@ HogQLParser::WithExprContext* HogQLParser::withExpr() { case 2: { _localctx = _tracker.createInstance(_localctx); enterOuterAlt(_localctx, 2); - setState(1003); + setState(1023); columnExpr(0); - setState(1004); + setState(1024); match(HogQLParser::AS); - setState(1005); + setState(1025); identifier(); break; } @@ -8697,12 +8798,12 @@ HogQLParser::ColumnIdentifierContext* HogQLParser::columnIdentifier() { exitRule(); }); try { - setState(1016); + setState(1036); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::LBRACE: { enterOuterAlt(_localctx, 1); - setState(1009); + setState(1029); placeholder(); break; } @@ -8802,14 +8903,14 @@ HogQLParser::ColumnIdentifierContext* HogQLParser::columnIdentifier() { case HogQLParser::YEAR: case HogQLParser::IDENTIFIER: { enterOuterAlt(_localctx, 2); - setState(1013); + setState(1033); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 114, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 120, _ctx)) { case 1: { - setState(1010); + setState(1030); tableIdentifier(); - setState(1011); + setState(1031); match(HogQLParser::DOT); break; } @@ -8817,7 +8918,7 @@ HogQLParser::ColumnIdentifierContext* HogQLParser::columnIdentifier() { default: break; } - setState(1015); + setState(1035); nestedIdentifier(); break; } @@ -8885,21 +8986,21 @@ HogQLParser::NestedIdentifierContext* HogQLParser::nestedIdentifier() { try { size_t alt; enterOuterAlt(_localctx, 1); - setState(1018); + setState(1038); identifier(); - setState(1023); + setState(1043); _errHandler->sync(this); - alt = getInterpreter()->adaptivePredict(_input, 116, _ctx); + alt = getInterpreter()->adaptivePredict(_input, 122, _ctx); while (alt != 2 && alt != atn::ATN::INVALID_ALT_NUMBER) { if (alt == 1) { - setState(1019); + setState(1039); match(HogQLParser::DOT); - setState(1020); + setState(1040); identifier(); } - setState(1025); + setState(1045); _errHandler->sync(this); - alt = getInterpreter()->adaptivePredict(_input, 116, _ctx); + alt = getInterpreter()->adaptivePredict(_input, 122, _ctx); } } @@ -9063,15 +9164,15 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { try { size_t alt; enterOuterAlt(_localctx, 1); - setState(1035); + setState(1055); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 117, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 123, _ctx)) { case 1: { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(1027); + setState(1047); tableIdentifier(); break; } @@ -9080,7 +9181,7 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(1028); + setState(1048); tableFunctionExpr(); break; } @@ -9089,11 +9190,11 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(1029); + setState(1049); match(HogQLParser::LPAREN); - setState(1030); + setState(1050); selectUnionStmt(); - setState(1031); + setState(1051); match(HogQLParser::RPAREN); break; } @@ -9102,7 +9203,7 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(1033); + setState(1053); hogqlxTagElement(); break; } @@ -9111,7 +9212,7 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { _localctx = _tracker.createInstance(_localctx); _ctx = _localctx; previousContext = _localctx; - setState(1034); + setState(1054); placeholder(); break; } @@ -9120,9 +9221,9 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { break; } _ctx->stop = _input->LT(-1); - setState(1045); + setState(1065); _errHandler->sync(this); - alt = getInterpreter()->adaptivePredict(_input, 119, _ctx); + alt = getInterpreter()->adaptivePredict(_input, 125, _ctx); while (alt != 2 && alt != atn::ATN::INVALID_ALT_NUMBER) { if (alt == 1) { if (!_parseListeners.empty()) @@ -9131,10 +9232,10 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { auto newContext = _tracker.createInstance(_tracker.createInstance(parentContext, parentState)); _localctx = newContext; pushNewRecursionContext(newContext, startState, RuleTableExpr); - setState(1037); + setState(1057); if (!(precpred(_ctx, 3))) throw FailedPredicateException(this, "precpred(_ctx, 3)"); - setState(1041); + setState(1061); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::DATE: @@ -9142,15 +9243,15 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { case HogQLParser::ID: case HogQLParser::KEY: case HogQLParser::IDENTIFIER: { - setState(1038); + setState(1058); alias(); break; } case HogQLParser::AS: { - setState(1039); + setState(1059); match(HogQLParser::AS); - setState(1040); + setState(1060); identifier(); break; } @@ -9159,9 +9260,9 @@ HogQLParser::TableExprContext* HogQLParser::tableExpr(int precedence) { throw NoViableAltException(this); } } - setState(1047); + setState(1067); _errHandler->sync(this); - alt = getInterpreter()->adaptivePredict(_input, 119, _ctx); + alt = getInterpreter()->adaptivePredict(_input, 125, _ctx); } } catch (RecognitionException &e) { @@ -9221,11 +9322,11 @@ HogQLParser::TableFunctionExprContext* HogQLParser::tableFunctionExpr() { }); try { enterOuterAlt(_localctx, 1); - setState(1048); + setState(1068); identifier(); - setState(1049); + setState(1069); match(HogQLParser::LPAREN); - setState(1051); + setState(1071); _errHandler->sync(this); _la = _input->LA(1); @@ -9233,10 +9334,10 @@ HogQLParser::TableFunctionExprContext* HogQLParser::tableFunctionExpr() { ((1ULL << _la) & -1125900443713538) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 64)) & 8076106347046764543) != 0) || ((((_la - 128) & ~ 0x3fULL) == 0) && ((1ULL << (_la - 128)) & 577) != 0)) { - setState(1050); + setState(1070); tableArgList(); } - setState(1053); + setState(1073); match(HogQLParser::RPAREN); } @@ -9293,14 +9394,14 @@ HogQLParser::TableIdentifierContext* HogQLParser::tableIdentifier() { }); try { enterOuterAlt(_localctx, 1); - setState(1058); + setState(1078); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 121, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 127, _ctx)) { case 1: { - setState(1055); + setState(1075); databaseIdentifier(); - setState(1056); + setState(1076); match(HogQLParser::DOT); break; } @@ -9308,7 +9409,7 @@ HogQLParser::TableIdentifierContext* HogQLParser::tableIdentifier() { default: break; } - setState(1060); + setState(1080); identifier(); } @@ -9370,17 +9471,17 @@ HogQLParser::TableArgListContext* HogQLParser::tableArgList() { }); try { enterOuterAlt(_localctx, 1); - setState(1062); + setState(1082); columnExpr(0); - setState(1067); + setState(1087); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::COMMA) { - setState(1063); + setState(1083); match(HogQLParser::COMMA); - setState(1064); + setState(1084); columnExpr(0); - setState(1069); + setState(1089); _errHandler->sync(this); _la = _input->LA(1); } @@ -9431,7 +9532,7 @@ HogQLParser::DatabaseIdentifierContext* HogQLParser::databaseIdentifier() { }); try { enterOuterAlt(_localctx, 1); - setState(1070); + setState(1090); identifier(); } @@ -9496,21 +9597,21 @@ HogQLParser::FloatingLiteralContext* HogQLParser::floatingLiteral() { exitRule(); }); try { - setState(1080); + setState(1100); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::FLOATING_LITERAL: { enterOuterAlt(_localctx, 1); - setState(1072); + setState(1092); match(HogQLParser::FLOATING_LITERAL); break; } case HogQLParser::DOT: { enterOuterAlt(_localctx, 2); - setState(1073); + setState(1093); match(HogQLParser::DOT); - setState(1074); + setState(1094); _la = _input->LA(1); if (!(_la == HogQLParser::OCTAL_LITERAL @@ -9526,16 +9627,16 @@ HogQLParser::FloatingLiteralContext* HogQLParser::floatingLiteral() { case HogQLParser::DECIMAL_LITERAL: { enterOuterAlt(_localctx, 3); - setState(1075); + setState(1095); match(HogQLParser::DECIMAL_LITERAL); - setState(1076); + setState(1096); match(HogQLParser::DOT); - setState(1078); + setState(1098); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 123, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 129, _ctx)) { case 1: { - setState(1077); + setState(1097); _la = _input->LA(1); if (!(_la == HogQLParser::OCTAL_LITERAL @@ -9634,14 +9735,14 @@ HogQLParser::NumberLiteralContext* HogQLParser::numberLiteral() { }); try { enterOuterAlt(_localctx, 1); - setState(1083); + setState(1103); _errHandler->sync(this); _la = _input->LA(1); if (_la == HogQLParser::DASH || _la == HogQLParser::PLUS) { - setState(1082); + setState(1102); _la = _input->LA(1); if (!(_la == HogQLParser::DASH @@ -9653,41 +9754,41 @@ HogQLParser::NumberLiteralContext* HogQLParser::numberLiteral() { consume(); } } - setState(1091); + setState(1111); _errHandler->sync(this); - switch (getInterpreter()->adaptivePredict(_input, 126, _ctx)) { + switch (getInterpreter()->adaptivePredict(_input, 132, _ctx)) { case 1: { - setState(1085); + setState(1105); floatingLiteral(); break; } case 2: { - setState(1086); + setState(1106); match(HogQLParser::OCTAL_LITERAL); break; } case 3: { - setState(1087); + setState(1107); match(HogQLParser::DECIMAL_LITERAL); break; } case 4: { - setState(1088); + setState(1108); match(HogQLParser::HEXADECIMAL_LITERAL); break; } case 5: { - setState(1089); + setState(1109); match(HogQLParser::INF); break; } case 6: { - setState(1090); + setState(1110); match(HogQLParser::NAN_SQL); break; } @@ -9749,7 +9850,7 @@ HogQLParser::LiteralContext* HogQLParser::literal() { exitRule(); }); try { - setState(1096); + setState(1116); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::INF: @@ -9762,21 +9863,21 @@ HogQLParser::LiteralContext* HogQLParser::literal() { case HogQLParser::DOT: case HogQLParser::PLUS: { enterOuterAlt(_localctx, 1); - setState(1093); + setState(1113); numberLiteral(); break; } case HogQLParser::STRING_LITERAL: { enterOuterAlt(_localctx, 2); - setState(1094); + setState(1114); match(HogQLParser::STRING_LITERAL); break; } case HogQLParser::NULL_SQL: { enterOuterAlt(_localctx, 3); - setState(1095); + setState(1115); match(HogQLParser::NULL_SQL); break; } @@ -9860,7 +9961,7 @@ HogQLParser::IntervalContext* HogQLParser::interval() { }); try { enterOuterAlt(_localctx, 1); - setState(1098); + setState(1118); _la = _input->LA(1); if (!((((_la & ~ 0x3fULL) == 0) && ((1ULL << _la) & 27021666484748288) != 0) || ((((_la - 68) & ~ 0x3fULL) == 0) && @@ -10255,7 +10356,7 @@ HogQLParser::KeywordContext* HogQLParser::keyword() { }); try { enterOuterAlt(_localctx, 1); - setState(1100); + setState(1120); _la = _input->LA(1); if (!((((_la & ~ 0x3fULL) == 0) && ((1ULL << _la) & -208293751046537218) != 0) || ((((_la - 64) & ~ 0x3fULL) == 0) && @@ -10326,7 +10427,7 @@ HogQLParser::KeywordForAliasContext* HogQLParser::keywordForAlias() { }); try { enterOuterAlt(_localctx, 1); - setState(1102); + setState(1122); _la = _input->LA(1); if (!((((_la & ~ 0x3fULL) == 0) && ((1ULL << _la) & 70506452090880) != 0))) { @@ -10386,12 +10487,12 @@ HogQLParser::AliasContext* HogQLParser::alias() { exitRule(); }); try { - setState(1106); + setState(1126); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::IDENTIFIER: { enterOuterAlt(_localctx, 1); - setState(1104); + setState(1124); match(HogQLParser::IDENTIFIER); break; } @@ -10401,7 +10502,7 @@ HogQLParser::AliasContext* HogQLParser::alias() { case HogQLParser::ID: case HogQLParser::KEY: { enterOuterAlt(_localctx, 2); - setState(1105); + setState(1125); keywordForAlias(); break; } @@ -10463,12 +10564,12 @@ HogQLParser::IdentifierContext* HogQLParser::identifier() { exitRule(); }); try { - setState(1111); + setState(1131); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::IDENTIFIER: { enterOuterAlt(_localctx, 1); - setState(1108); + setState(1128); match(HogQLParser::IDENTIFIER); break; } @@ -10482,7 +10583,7 @@ HogQLParser::IdentifierContext* HogQLParser::identifier() { case HogQLParser::WEEK: case HogQLParser::YEAR: { enterOuterAlt(_localctx, 2); - setState(1109); + setState(1129); interval(); break; } @@ -10573,7 +10674,7 @@ HogQLParser::IdentifierContext* HogQLParser::identifier() { case HogQLParser::WINDOW: case HogQLParser::WITH: { enterOuterAlt(_localctx, 3); - setState(1110); + setState(1130); keyword(); break; } @@ -10636,11 +10737,11 @@ HogQLParser::EnumValueContext* HogQLParser::enumValue() { }); try { enterOuterAlt(_localctx, 1); - setState(1113); + setState(1133); string(); - setState(1114); + setState(1134); match(HogQLParser::EQ_SINGLE); - setState(1115); + setState(1135); numberLiteral(); } @@ -10697,11 +10798,11 @@ HogQLParser::PlaceholderContext* HogQLParser::placeholder() { }); try { enterOuterAlt(_localctx, 1); - setState(1117); + setState(1137); match(HogQLParser::LBRACE); - setState(1118); + setState(1138); identifier(); - setState(1119); + setState(1139); match(HogQLParser::RBRACE); } @@ -10753,19 +10854,19 @@ HogQLParser::StringContext* HogQLParser::string() { exitRule(); }); try { - setState(1123); + setState(1143); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::STRING_LITERAL: { enterOuterAlt(_localctx, 1); - setState(1121); + setState(1141); match(HogQLParser::STRING_LITERAL); break; } case HogQLParser::QUOTE_SINGLE_TEMPLATE: { enterOuterAlt(_localctx, 2); - setState(1122); + setState(1142); templateString(); break; } @@ -10833,21 +10934,21 @@ HogQLParser::TemplateStringContext* HogQLParser::templateString() { }); try { enterOuterAlt(_localctx, 1); - setState(1125); + setState(1145); match(HogQLParser::QUOTE_SINGLE_TEMPLATE); - setState(1129); + setState(1149); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::STRING_TEXT || _la == HogQLParser::STRING_ESCAPE_TRIGGER) { - setState(1126); + setState(1146); stringContents(); - setState(1131); + setState(1151); _errHandler->sync(this); _la = _input->LA(1); } - setState(1132); + setState(1152); match(HogQLParser::QUOTE_SINGLE); } @@ -10907,23 +11008,23 @@ HogQLParser::StringContentsContext* HogQLParser::stringContents() { exitRule(); }); try { - setState(1139); + setState(1159); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::STRING_ESCAPE_TRIGGER: { enterOuterAlt(_localctx, 1); - setState(1134); + setState(1154); match(HogQLParser::STRING_ESCAPE_TRIGGER); - setState(1135); + setState(1155); columnExpr(0); - setState(1136); + setState(1156); match(HogQLParser::RBRACE); break; } case HogQLParser::STRING_TEXT: { enterOuterAlt(_localctx, 2); - setState(1138); + setState(1158); match(HogQLParser::STRING_TEXT); break; } @@ -10991,21 +11092,21 @@ HogQLParser::FullTemplateStringContext* HogQLParser::fullTemplateString() { }); try { enterOuterAlt(_localctx, 1); - setState(1141); + setState(1161); match(HogQLParser::QUOTE_SINGLE_TEMPLATE_FULL); - setState(1145); + setState(1165); _errHandler->sync(this); _la = _input->LA(1); while (_la == HogQLParser::FULL_STRING_TEXT || _la == HogQLParser::FULL_STRING_ESCAPE_TRIGGER) { - setState(1142); + setState(1162); stringContentsFull(); - setState(1147); + setState(1167); _errHandler->sync(this); _la = _input->LA(1); } - setState(1148); + setState(1168); match(HogQLParser::EOF); } @@ -11065,23 +11166,23 @@ HogQLParser::StringContentsFullContext* HogQLParser::stringContentsFull() { exitRule(); }); try { - setState(1155); + setState(1175); _errHandler->sync(this); switch (_input->LA(1)) { case HogQLParser::FULL_STRING_ESCAPE_TRIGGER: { enterOuterAlt(_localctx, 1); - setState(1150); + setState(1170); match(HogQLParser::FULL_STRING_ESCAPE_TRIGGER); - setState(1151); + setState(1171); columnExpr(0); - setState(1152); + setState(1172); match(HogQLParser::RBRACE); break; } case HogQLParser::FULL_STRING_TEXT: { enterOuterAlt(_localctx, 2); - setState(1154); + setState(1174); match(HogQLParser::FULL_STRING_TEXT); break; } diff --git a/hogql_parser/HogQLParser.h b/hogql_parser/HogQLParser.h index 174d2572e57361..94f46d07b45622 100644 --- a/hogql_parser/HogQLParser.h +++ b/hogql_parser/HogQLParser.h @@ -1439,9 +1439,13 @@ class HogQLParser : public antlr4::Parser { std::vector identifier(); IdentifierContext* identifier(size_t i); antlr4::tree::TerminalNode *OVER(); - antlr4::tree::TerminalNode *LPAREN(); - antlr4::tree::TerminalNode *RPAREN(); + std::vector LPAREN(); + antlr4::tree::TerminalNode* LPAREN(size_t i); + std::vector RPAREN(); + antlr4::tree::TerminalNode* RPAREN(size_t i); ColumnExprListContext *columnExprList(); + antlr4::tree::TerminalNode *DISTINCT(); + ColumnArgListContext *columnArgList(); virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override; }; @@ -1635,6 +1639,8 @@ class HogQLParser : public antlr4::Parser { std::vector RPAREN(); antlr4::tree::TerminalNode* RPAREN(size_t i); ColumnExprListContext *columnExprList(); + antlr4::tree::TerminalNode *DISTINCT(); + ColumnArgListContext *columnArgList(); virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override; }; diff --git a/hogql_parser/HogQLParser.interp b/hogql_parser/HogQLParser.interp index a2f030a7eb8fa6..086eca220c32f7 100644 --- a/hogql_parser/HogQLParser.interp +++ b/hogql_parser/HogQLParser.interp @@ -399,4 +399,4 @@ stringContentsFull atn: -[4, 1, 154, 1158, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 1, 0, 5, 0, 168, 8, 0, 10, 0, 12, 0, 171, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 177, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 186, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 5, 5, 199, 8, 5, 10, 5, 12, 5, 202, 9, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 213, 8, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 225, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 241, 8, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 5, 13, 250, 8, 13, 10, 13, 12, 13, 253, 9, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 5, 15, 264, 8, 15, 10, 15, 12, 15, 267, 9, 15, 1, 16, 1, 16, 1, 16, 3, 16, 272, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 5, 17, 280, 8, 17, 10, 17, 12, 17, 283, 9, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 3, 18, 291, 8, 18, 1, 19, 3, 19, 294, 8, 19, 1, 19, 1, 19, 3, 19, 298, 8, 19, 1, 19, 3, 19, 301, 8, 19, 1, 19, 1, 19, 3, 19, 305, 8, 19, 1, 19, 3, 19, 308, 8, 19, 1, 19, 3, 19, 311, 8, 19, 1, 19, 3, 19, 314, 8, 19, 1, 19, 3, 19, 317, 8, 19, 1, 19, 1, 19, 3, 19, 321, 8, 19, 1, 19, 1, 19, 3, 19, 325, 8, 19, 1, 19, 3, 19, 328, 8, 19, 1, 19, 3, 19, 331, 8, 19, 1, 19, 3, 19, 334, 8, 19, 1, 19, 1, 19, 3, 19, 338, 8, 19, 1, 19, 3, 19, 341, 8, 19, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 350, 8, 21, 1, 22, 1, 22, 1, 22, 1, 23, 3, 23, 356, 8, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 375, 8, 24, 10, 24, 12, 24, 378, 9, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 3, 27, 394, 8, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 411, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 417, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 423, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 434, 8, 31, 3, 31, 436, 8, 31, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 3, 34, 447, 8, 34, 1, 34, 3, 34, 450, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 456, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 464, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 470, 8, 34, 10, 34, 12, 34, 473, 9, 34, 1, 35, 3, 35, 476, 8, 35, 1, 35, 1, 35, 1, 35, 3, 35, 481, 8, 35, 1, 35, 3, 35, 484, 8, 35, 1, 35, 3, 35, 487, 8, 35, 1, 35, 1, 35, 3, 35, 491, 8, 35, 1, 35, 1, 35, 3, 35, 495, 8, 35, 1, 35, 3, 35, 498, 8, 35, 3, 35, 500, 8, 35, 1, 35, 3, 35, 503, 8, 35, 1, 35, 1, 35, 3, 35, 507, 8, 35, 1, 35, 1, 35, 3, 35, 511, 8, 35, 1, 35, 3, 35, 514, 8, 35, 3, 35, 516, 8, 35, 3, 35, 518, 8, 35, 1, 36, 1, 36, 1, 36, 3, 36, 523, 8, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 3, 37, 534, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 540, 8, 38, 1, 39, 1, 39, 1, 39, 5, 39, 545, 8, 39, 10, 39, 12, 39, 548, 9, 39, 1, 40, 1, 40, 3, 40, 552, 8, 40, 1, 40, 1, 40, 3, 40, 556, 8, 40, 1, 40, 1, 40, 3, 40, 560, 8, 40, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 566, 8, 41, 3, 41, 568, 8, 41, 1, 42, 1, 42, 1, 42, 5, 42, 573, 8, 42, 10, 42, 12, 42, 576, 9, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 3, 44, 583, 8, 44, 1, 44, 3, 44, 586, 8, 44, 1, 44, 3, 44, 589, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 3, 48, 608, 8, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 622, 8, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 636, 8, 51, 10, 51, 12, 51, 639, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 648, 8, 51, 10, 51, 12, 51, 651, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 660, 8, 51, 10, 51, 12, 51, 663, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, 670, 8, 51, 1, 51, 1, 51, 3, 51, 674, 8, 51, 1, 52, 1, 52, 1, 52, 5, 52, 679, 8, 52, 10, 52, 12, 52, 682, 9, 52, 1, 53, 1, 53, 1, 53, 3, 53, 687, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 4, 53, 694, 8, 53, 11, 53, 12, 53, 695, 1, 53, 1, 53, 3, 53, 700, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 724, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 741, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 753, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 763, 8, 53, 1, 53, 3, 53, 766, 8, 53, 1, 53, 1, 53, 3, 53, 770, 8, 53, 1, 53, 3, 53, 773, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 787, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 804, 8, 53, 1, 53, 1, 53, 1, 53, 3, 53, 809, 8, 53, 1, 53, 1, 53, 3, 53, 813, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 819, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 826, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 838, 8, 53, 1, 53, 1, 53, 3, 53, 842, 8, 53, 1, 53, 3, 53, 845, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 854, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 868, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 895, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 904, 8, 53, 5, 53, 906, 8, 53, 10, 53, 12, 53, 909, 9, 53, 1, 54, 1, 54, 1, 54, 5, 54, 914, 8, 54, 10, 54, 12, 54, 917, 9, 54, 1, 55, 1, 55, 3, 55, 921, 8, 55, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 927, 8, 56, 10, 56, 12, 56, 930, 9, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 937, 8, 56, 10, 56, 12, 56, 940, 9, 56, 3, 56, 942, 8, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 5, 57, 950, 8, 57, 10, 57, 12, 57, 953, 9, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 5, 57, 961, 8, 57, 10, 57, 12, 57, 964, 9, 57, 1, 57, 1, 57, 3, 57, 968, 8, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 3, 57, 975, 8, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 988, 8, 58, 1, 59, 1, 59, 1, 59, 5, 59, 993, 8, 59, 10, 59, 12, 59, 996, 9, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 1008, 8, 60, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 1014, 8, 61, 1, 61, 3, 61, 1017, 8, 61, 1, 62, 1, 62, 1, 62, 5, 62, 1022, 8, 62, 10, 62, 12, 62, 1025, 9, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1036, 8, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1042, 8, 63, 5, 63, 1044, 8, 63, 10, 63, 12, 63, 1047, 9, 63, 1, 64, 1, 64, 1, 64, 3, 64, 1052, 8, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 3, 65, 1059, 8, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 5, 66, 1066, 8, 66, 10, 66, 12, 66, 1069, 9, 66, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 3, 68, 1079, 8, 68, 3, 68, 1081, 8, 68, 1, 69, 3, 69, 1084, 8, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 3, 69, 1092, 8, 69, 1, 70, 1, 70, 1, 70, 3, 70, 1097, 8, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 3, 74, 1107, 8, 74, 1, 75, 1, 75, 1, 75, 3, 75, 1112, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 3, 78, 1124, 8, 78, 1, 79, 1, 79, 5, 79, 1128, 8, 79, 10, 79, 12, 79, 1131, 9, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 3, 80, 1140, 8, 80, 1, 81, 1, 81, 5, 81, 1144, 8, 81, 10, 81, 12, 81, 1147, 9, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 3, 82, 1156, 8, 82, 1, 82, 0, 3, 68, 106, 126, 83, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 0, 16, 2, 0, 17, 17, 72, 72, 2, 0, 42, 42, 49, 49, 3, 0, 1, 1, 4, 4, 8, 8, 4, 0, 1, 1, 3, 4, 8, 8, 78, 78, 2, 0, 49, 49, 71, 71, 2, 0, 1, 1, 4, 4, 2, 0, 7, 7, 21, 22, 2, 0, 28, 28, 47, 47, 2, 0, 69, 69, 74, 74, 3, 0, 10, 10, 48, 48, 87, 87, 2, 0, 39, 39, 51, 51, 1, 0, 103, 104, 2, 0, 114, 114, 134, 134, 7, 0, 20, 20, 36, 36, 53, 54, 68, 68, 76, 76, 93, 93, 99, 99, 12, 0, 1, 19, 21, 28, 30, 35, 37, 40, 42, 49, 51, 52, 56, 56, 58, 67, 69, 75, 77, 92, 94, 95, 97, 98, 4, 0, 19, 19, 28, 28, 37, 37, 46, 46, 1288, 0, 169, 1, 0, 0, 0, 2, 176, 1, 0, 0, 0, 4, 178, 1, 0, 0, 0, 6, 180, 1, 0, 0, 0, 8, 189, 1, 0, 0, 0, 10, 195, 1, 0, 0, 0, 12, 212, 1, 0, 0, 0, 14, 214, 1, 0, 0, 0, 16, 217, 1, 0, 0, 0, 18, 226, 1, 0, 0, 0, 20, 232, 1, 0, 0, 0, 22, 236, 1, 0, 0, 0, 24, 245, 1, 0, 0, 0, 26, 247, 1, 0, 0, 0, 28, 256, 1, 0, 0, 0, 30, 260, 1, 0, 0, 0, 32, 271, 1, 0, 0, 0, 34, 275, 1, 0, 0, 0, 36, 290, 1, 0, 0, 0, 38, 293, 1, 0, 0, 0, 40, 342, 1, 0, 0, 0, 42, 345, 1, 0, 0, 0, 44, 351, 1, 0, 0, 0, 46, 355, 1, 0, 0, 0, 48, 361, 1, 0, 0, 0, 50, 379, 1, 0, 0, 0, 52, 382, 1, 0, 0, 0, 54, 385, 1, 0, 0, 0, 56, 395, 1, 0, 0, 0, 58, 398, 1, 0, 0, 0, 60, 402, 1, 0, 0, 0, 62, 435, 1, 0, 0, 0, 64, 437, 1, 0, 0, 0, 66, 440, 1, 0, 0, 0, 68, 455, 1, 0, 0, 0, 70, 517, 1, 0, 0, 0, 72, 522, 1, 0, 0, 0, 74, 533, 1, 0, 0, 0, 76, 535, 1, 0, 0, 0, 78, 541, 1, 0, 0, 0, 80, 549, 1, 0, 0, 0, 82, 567, 1, 0, 0, 0, 84, 569, 1, 0, 0, 0, 86, 577, 1, 0, 0, 0, 88, 582, 1, 0, 0, 0, 90, 590, 1, 0, 0, 0, 92, 594, 1, 0, 0, 0, 94, 598, 1, 0, 0, 0, 96, 607, 1, 0, 0, 0, 98, 621, 1, 0, 0, 0, 100, 623, 1, 0, 0, 0, 102, 673, 1, 0, 0, 0, 104, 675, 1, 0, 0, 0, 106, 812, 1, 0, 0, 0, 108, 910, 1, 0, 0, 0, 110, 920, 1, 0, 0, 0, 112, 941, 1, 0, 0, 0, 114, 974, 1, 0, 0, 0, 116, 987, 1, 0, 0, 0, 118, 989, 1, 0, 0, 0, 120, 1007, 1, 0, 0, 0, 122, 1016, 1, 0, 0, 0, 124, 1018, 1, 0, 0, 0, 126, 1035, 1, 0, 0, 0, 128, 1048, 1, 0, 0, 0, 130, 1058, 1, 0, 0, 0, 132, 1062, 1, 0, 0, 0, 134, 1070, 1, 0, 0, 0, 136, 1080, 1, 0, 0, 0, 138, 1083, 1, 0, 0, 0, 140, 1096, 1, 0, 0, 0, 142, 1098, 1, 0, 0, 0, 144, 1100, 1, 0, 0, 0, 146, 1102, 1, 0, 0, 0, 148, 1106, 1, 0, 0, 0, 150, 1111, 1, 0, 0, 0, 152, 1113, 1, 0, 0, 0, 154, 1117, 1, 0, 0, 0, 156, 1123, 1, 0, 0, 0, 158, 1125, 1, 0, 0, 0, 160, 1139, 1, 0, 0, 0, 162, 1141, 1, 0, 0, 0, 164, 1155, 1, 0, 0, 0, 166, 168, 3, 2, 1, 0, 167, 166, 1, 0, 0, 0, 168, 171, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 169, 170, 1, 0, 0, 0, 170, 172, 1, 0, 0, 0, 171, 169, 1, 0, 0, 0, 172, 173, 5, 0, 0, 1, 173, 1, 1, 0, 0, 0, 174, 177, 3, 6, 3, 0, 175, 177, 3, 12, 6, 0, 176, 174, 1, 0, 0, 0, 176, 175, 1, 0, 0, 0, 177, 3, 1, 0, 0, 0, 178, 179, 3, 106, 53, 0, 179, 5, 1, 0, 0, 0, 180, 181, 5, 50, 0, 0, 181, 185, 3, 150, 75, 0, 182, 183, 5, 111, 0, 0, 183, 184, 5, 118, 0, 0, 184, 186, 3, 4, 2, 0, 185, 182, 1, 0, 0, 0, 185, 186, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 188, 5, 145, 0, 0, 188, 7, 1, 0, 0, 0, 189, 190, 3, 4, 2, 0, 190, 191, 5, 111, 0, 0, 191, 192, 5, 118, 0, 0, 192, 193, 3, 4, 2, 0, 193, 194, 5, 145, 0, 0, 194, 9, 1, 0, 0, 0, 195, 200, 3, 150, 75, 0, 196, 197, 5, 112, 0, 0, 197, 199, 3, 150, 75, 0, 198, 196, 1, 0, 0, 0, 199, 202, 1, 0, 0, 0, 200, 198, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 11, 1, 0, 0, 0, 202, 200, 1, 0, 0, 0, 203, 213, 3, 20, 10, 0, 204, 213, 3, 24, 12, 0, 205, 213, 3, 14, 7, 0, 206, 213, 3, 16, 8, 0, 207, 213, 3, 18, 9, 0, 208, 213, 3, 22, 11, 0, 209, 213, 3, 8, 4, 0, 210, 213, 3, 20, 10, 0, 211, 213, 3, 26, 13, 0, 212, 203, 1, 0, 0, 0, 212, 204, 1, 0, 0, 0, 212, 205, 1, 0, 0, 0, 212, 206, 1, 0, 0, 0, 212, 207, 1, 0, 0, 0, 212, 208, 1, 0, 0, 0, 212, 209, 1, 0, 0, 0, 212, 210, 1, 0, 0, 0, 212, 211, 1, 0, 0, 0, 213, 13, 1, 0, 0, 0, 214, 215, 3, 4, 2, 0, 215, 216, 5, 145, 0, 0, 216, 15, 1, 0, 0, 0, 217, 218, 5, 38, 0, 0, 218, 219, 5, 126, 0, 0, 219, 220, 3, 4, 2, 0, 220, 221, 5, 144, 0, 0, 221, 224, 3, 12, 6, 0, 222, 223, 5, 24, 0, 0, 223, 225, 3, 12, 6, 0, 224, 222, 1, 0, 0, 0, 224, 225, 1, 0, 0, 0, 225, 17, 1, 0, 0, 0, 226, 227, 5, 96, 0, 0, 227, 228, 5, 126, 0, 0, 228, 229, 3, 4, 2, 0, 229, 230, 5, 144, 0, 0, 230, 231, 3, 12, 6, 0, 231, 19, 1, 0, 0, 0, 232, 233, 5, 70, 0, 0, 233, 234, 3, 4, 2, 0, 234, 235, 5, 145, 0, 0, 235, 21, 1, 0, 0, 0, 236, 237, 5, 29, 0, 0, 237, 238, 3, 150, 75, 0, 238, 240, 5, 126, 0, 0, 239, 241, 3, 10, 5, 0, 240, 239, 1, 0, 0, 0, 240, 241, 1, 0, 0, 0, 241, 242, 1, 0, 0, 0, 242, 243, 5, 144, 0, 0, 243, 244, 3, 26, 13, 0, 244, 23, 1, 0, 0, 0, 245, 246, 5, 145, 0, 0, 246, 25, 1, 0, 0, 0, 247, 251, 5, 124, 0, 0, 248, 250, 3, 2, 1, 0, 249, 248, 1, 0, 0, 0, 250, 253, 1, 0, 0, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 254, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 254, 255, 5, 142, 0, 0, 255, 27, 1, 0, 0, 0, 256, 257, 3, 4, 2, 0, 257, 258, 5, 111, 0, 0, 258, 259, 3, 4, 2, 0, 259, 29, 1, 0, 0, 0, 260, 265, 3, 28, 14, 0, 261, 262, 5, 112, 0, 0, 262, 264, 3, 28, 14, 0, 263, 261, 1, 0, 0, 0, 264, 267, 1, 0, 0, 0, 265, 263, 1, 0, 0, 0, 265, 266, 1, 0, 0, 0, 266, 31, 1, 0, 0, 0, 267, 265, 1, 0, 0, 0, 268, 272, 3, 34, 17, 0, 269, 272, 3, 38, 19, 0, 270, 272, 3, 114, 57, 0, 271, 268, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 271, 270, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 274, 5, 0, 0, 1, 274, 33, 1, 0, 0, 0, 275, 281, 3, 36, 18, 0, 276, 277, 5, 91, 0, 0, 277, 278, 5, 1, 0, 0, 278, 280, 3, 36, 18, 0, 279, 276, 1, 0, 0, 0, 280, 283, 1, 0, 0, 0, 281, 279, 1, 0, 0, 0, 281, 282, 1, 0, 0, 0, 282, 35, 1, 0, 0, 0, 283, 281, 1, 0, 0, 0, 284, 291, 3, 38, 19, 0, 285, 286, 5, 126, 0, 0, 286, 287, 3, 34, 17, 0, 287, 288, 5, 144, 0, 0, 288, 291, 1, 0, 0, 0, 289, 291, 3, 154, 77, 0, 290, 284, 1, 0, 0, 0, 290, 285, 1, 0, 0, 0, 290, 289, 1, 0, 0, 0, 291, 37, 1, 0, 0, 0, 292, 294, 3, 40, 20, 0, 293, 292, 1, 0, 0, 0, 293, 294, 1, 0, 0, 0, 294, 295, 1, 0, 0, 0, 295, 297, 5, 77, 0, 0, 296, 298, 5, 23, 0, 0, 297, 296, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 300, 1, 0, 0, 0, 299, 301, 3, 42, 21, 0, 300, 299, 1, 0, 0, 0, 300, 301, 1, 0, 0, 0, 301, 302, 1, 0, 0, 0, 302, 304, 3, 104, 52, 0, 303, 305, 3, 44, 22, 0, 304, 303, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 307, 1, 0, 0, 0, 306, 308, 3, 46, 23, 0, 307, 306, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 310, 1, 0, 0, 0, 309, 311, 3, 50, 25, 0, 310, 309, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 313, 1, 0, 0, 0, 312, 314, 3, 52, 26, 0, 313, 312, 1, 0, 0, 0, 313, 314, 1, 0, 0, 0, 314, 316, 1, 0, 0, 0, 315, 317, 3, 54, 27, 0, 316, 315, 1, 0, 0, 0, 316, 317, 1, 0, 0, 0, 317, 320, 1, 0, 0, 0, 318, 319, 5, 98, 0, 0, 319, 321, 7, 0, 0, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 324, 1, 0, 0, 0, 322, 323, 5, 98, 0, 0, 323, 325, 5, 86, 0, 0, 324, 322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 327, 1, 0, 0, 0, 326, 328, 3, 56, 28, 0, 327, 326, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 330, 1, 0, 0, 0, 329, 331, 3, 48, 24, 0, 330, 329, 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 333, 1, 0, 0, 0, 332, 334, 3, 58, 29, 0, 333, 332, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 337, 1, 0, 0, 0, 335, 338, 3, 62, 31, 0, 336, 338, 3, 64, 32, 0, 337, 335, 1, 0, 0, 0, 337, 336, 1, 0, 0, 0, 337, 338, 1, 0, 0, 0, 338, 340, 1, 0, 0, 0, 339, 341, 3, 66, 33, 0, 340, 339, 1, 0, 0, 0, 340, 341, 1, 0, 0, 0, 341, 39, 1, 0, 0, 0, 342, 343, 5, 98, 0, 0, 343, 344, 3, 118, 59, 0, 344, 41, 1, 0, 0, 0, 345, 346, 5, 85, 0, 0, 346, 349, 5, 104, 0, 0, 347, 348, 5, 98, 0, 0, 348, 350, 5, 82, 0, 0, 349, 347, 1, 0, 0, 0, 349, 350, 1, 0, 0, 0, 350, 43, 1, 0, 0, 0, 351, 352, 5, 32, 0, 0, 352, 353, 3, 68, 34, 0, 353, 45, 1, 0, 0, 0, 354, 356, 7, 1, 0, 0, 355, 354, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 358, 5, 5, 0, 0, 358, 359, 5, 45, 0, 0, 359, 360, 3, 104, 52, 0, 360, 47, 1, 0, 0, 0, 361, 362, 5, 97, 0, 0, 362, 363, 3, 150, 75, 0, 363, 364, 5, 6, 0, 0, 364, 365, 5, 126, 0, 0, 365, 366, 3, 88, 44, 0, 366, 376, 5, 144, 0, 0, 367, 368, 5, 112, 0, 0, 368, 369, 3, 150, 75, 0, 369, 370, 5, 6, 0, 0, 370, 371, 5, 126, 0, 0, 371, 372, 3, 88, 44, 0, 372, 373, 5, 144, 0, 0, 373, 375, 1, 0, 0, 0, 374, 367, 1, 0, 0, 0, 375, 378, 1, 0, 0, 0, 376, 374, 1, 0, 0, 0, 376, 377, 1, 0, 0, 0, 377, 49, 1, 0, 0, 0, 378, 376, 1, 0, 0, 0, 379, 380, 5, 67, 0, 0, 380, 381, 3, 106, 53, 0, 381, 51, 1, 0, 0, 0, 382, 383, 5, 95, 0, 0, 383, 384, 3, 106, 53, 0, 384, 53, 1, 0, 0, 0, 385, 386, 5, 34, 0, 0, 386, 393, 5, 11, 0, 0, 387, 388, 7, 0, 0, 0, 388, 389, 5, 126, 0, 0, 389, 390, 3, 104, 52, 0, 390, 391, 5, 144, 0, 0, 391, 394, 1, 0, 0, 0, 392, 394, 3, 104, 52, 0, 393, 387, 1, 0, 0, 0, 393, 392, 1, 0, 0, 0, 394, 55, 1, 0, 0, 0, 395, 396, 5, 35, 0, 0, 396, 397, 3, 106, 53, 0, 397, 57, 1, 0, 0, 0, 398, 399, 5, 62, 0, 0, 399, 400, 5, 11, 0, 0, 400, 401, 3, 78, 39, 0, 401, 59, 1, 0, 0, 0, 402, 403, 5, 62, 0, 0, 403, 404, 5, 11, 0, 0, 404, 405, 3, 104, 52, 0, 405, 61, 1, 0, 0, 0, 406, 407, 5, 52, 0, 0, 407, 410, 3, 106, 53, 0, 408, 409, 5, 112, 0, 0, 409, 411, 3, 106, 53, 0, 410, 408, 1, 0, 0, 0, 410, 411, 1, 0, 0, 0, 411, 416, 1, 0, 0, 0, 412, 413, 5, 98, 0, 0, 413, 417, 5, 82, 0, 0, 414, 415, 5, 11, 0, 0, 415, 417, 3, 104, 52, 0, 416, 412, 1, 0, 0, 0, 416, 414, 1, 0, 0, 0, 416, 417, 1, 0, 0, 0, 417, 436, 1, 0, 0, 0, 418, 419, 5, 52, 0, 0, 419, 422, 3, 106, 53, 0, 420, 421, 5, 98, 0, 0, 421, 423, 5, 82, 0, 0, 422, 420, 1, 0, 0, 0, 422, 423, 1, 0, 0, 0, 423, 424, 1, 0, 0, 0, 424, 425, 5, 59, 0, 0, 425, 426, 3, 106, 53, 0, 426, 436, 1, 0, 0, 0, 427, 428, 5, 52, 0, 0, 428, 429, 3, 106, 53, 0, 429, 430, 5, 59, 0, 0, 430, 433, 3, 106, 53, 0, 431, 432, 5, 11, 0, 0, 432, 434, 3, 104, 52, 0, 433, 431, 1, 0, 0, 0, 433, 434, 1, 0, 0, 0, 434, 436, 1, 0, 0, 0, 435, 406, 1, 0, 0, 0, 435, 418, 1, 0, 0, 0, 435, 427, 1, 0, 0, 0, 436, 63, 1, 0, 0, 0, 437, 438, 5, 59, 0, 0, 438, 439, 3, 106, 53, 0, 439, 65, 1, 0, 0, 0, 440, 441, 5, 79, 0, 0, 441, 442, 3, 84, 42, 0, 442, 67, 1, 0, 0, 0, 443, 444, 6, 34, -1, 0, 444, 446, 3, 126, 63, 0, 445, 447, 5, 27, 0, 0, 446, 445, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 449, 1, 0, 0, 0, 448, 450, 3, 76, 38, 0, 449, 448, 1, 0, 0, 0, 449, 450, 1, 0, 0, 0, 450, 456, 1, 0, 0, 0, 451, 452, 5, 126, 0, 0, 452, 453, 3, 68, 34, 0, 453, 454, 5, 144, 0, 0, 454, 456, 1, 0, 0, 0, 455, 443, 1, 0, 0, 0, 455, 451, 1, 0, 0, 0, 456, 471, 1, 0, 0, 0, 457, 458, 10, 3, 0, 0, 458, 459, 3, 72, 36, 0, 459, 460, 3, 68, 34, 4, 460, 470, 1, 0, 0, 0, 461, 463, 10, 4, 0, 0, 462, 464, 3, 70, 35, 0, 463, 462, 1, 0, 0, 0, 463, 464, 1, 0, 0, 0, 464, 465, 1, 0, 0, 0, 465, 466, 5, 45, 0, 0, 466, 467, 3, 68, 34, 0, 467, 468, 3, 74, 37, 0, 468, 470, 1, 0, 0, 0, 469, 457, 1, 0, 0, 0, 469, 461, 1, 0, 0, 0, 470, 473, 1, 0, 0, 0, 471, 469, 1, 0, 0, 0, 471, 472, 1, 0, 0, 0, 472, 69, 1, 0, 0, 0, 473, 471, 1, 0, 0, 0, 474, 476, 7, 2, 0, 0, 475, 474, 1, 0, 0, 0, 475, 476, 1, 0, 0, 0, 476, 477, 1, 0, 0, 0, 477, 484, 5, 42, 0, 0, 478, 480, 5, 42, 0, 0, 479, 481, 7, 2, 0, 0, 480, 479, 1, 0, 0, 0, 480, 481, 1, 0, 0, 0, 481, 484, 1, 0, 0, 0, 482, 484, 7, 2, 0, 0, 483, 475, 1, 0, 0, 0, 483, 478, 1, 0, 0, 0, 483, 482, 1, 0, 0, 0, 484, 518, 1, 0, 0, 0, 485, 487, 7, 3, 0, 0, 486, 485, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 488, 1, 0, 0, 0, 488, 490, 7, 4, 0, 0, 489, 491, 5, 63, 0, 0, 490, 489, 1, 0, 0, 0, 490, 491, 1, 0, 0, 0, 491, 500, 1, 0, 0, 0, 492, 494, 7, 4, 0, 0, 493, 495, 5, 63, 0, 0, 494, 493, 1, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, 497, 1, 0, 0, 0, 496, 498, 7, 3, 0, 0, 497, 496, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 500, 1, 0, 0, 0, 499, 486, 1, 0, 0, 0, 499, 492, 1, 0, 0, 0, 500, 518, 1, 0, 0, 0, 501, 503, 7, 5, 0, 0, 502, 501, 1, 0, 0, 0, 502, 503, 1, 0, 0, 0, 503, 504, 1, 0, 0, 0, 504, 506, 5, 33, 0, 0, 505, 507, 5, 63, 0, 0, 506, 505, 1, 0, 0, 0, 506, 507, 1, 0, 0, 0, 507, 516, 1, 0, 0, 0, 508, 510, 5, 33, 0, 0, 509, 511, 5, 63, 0, 0, 510, 509, 1, 0, 0, 0, 510, 511, 1, 0, 0, 0, 511, 513, 1, 0, 0, 0, 512, 514, 7, 5, 0, 0, 513, 512, 1, 0, 0, 0, 513, 514, 1, 0, 0, 0, 514, 516, 1, 0, 0, 0, 515, 502, 1, 0, 0, 0, 515, 508, 1, 0, 0, 0, 516, 518, 1, 0, 0, 0, 517, 483, 1, 0, 0, 0, 517, 499, 1, 0, 0, 0, 517, 515, 1, 0, 0, 0, 518, 71, 1, 0, 0, 0, 519, 520, 5, 16, 0, 0, 520, 523, 5, 45, 0, 0, 521, 523, 5, 112, 0, 0, 522, 519, 1, 0, 0, 0, 522, 521, 1, 0, 0, 0, 523, 73, 1, 0, 0, 0, 524, 525, 5, 60, 0, 0, 525, 534, 3, 104, 52, 0, 526, 527, 5, 92, 0, 0, 527, 528, 5, 126, 0, 0, 528, 529, 3, 104, 52, 0, 529, 530, 5, 144, 0, 0, 530, 534, 1, 0, 0, 0, 531, 532, 5, 92, 0, 0, 532, 534, 3, 104, 52, 0, 533, 524, 1, 0, 0, 0, 533, 526, 1, 0, 0, 0, 533, 531, 1, 0, 0, 0, 534, 75, 1, 0, 0, 0, 535, 536, 5, 75, 0, 0, 536, 539, 3, 82, 41, 0, 537, 538, 5, 59, 0, 0, 538, 540, 3, 82, 41, 0, 539, 537, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 77, 1, 0, 0, 0, 541, 546, 3, 80, 40, 0, 542, 543, 5, 112, 0, 0, 543, 545, 3, 80, 40, 0, 544, 542, 1, 0, 0, 0, 545, 548, 1, 0, 0, 0, 546, 544, 1, 0, 0, 0, 546, 547, 1, 0, 0, 0, 547, 79, 1, 0, 0, 0, 548, 546, 1, 0, 0, 0, 549, 551, 3, 106, 53, 0, 550, 552, 7, 6, 0, 0, 551, 550, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 555, 1, 0, 0, 0, 553, 554, 5, 58, 0, 0, 554, 556, 7, 7, 0, 0, 555, 553, 1, 0, 0, 0, 555, 556, 1, 0, 0, 0, 556, 559, 1, 0, 0, 0, 557, 558, 5, 15, 0, 0, 558, 560, 5, 106, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 81, 1, 0, 0, 0, 561, 568, 3, 154, 77, 0, 562, 565, 3, 138, 69, 0, 563, 564, 5, 146, 0, 0, 564, 566, 3, 138, 69, 0, 565, 563, 1, 0, 0, 0, 565, 566, 1, 0, 0, 0, 566, 568, 1, 0, 0, 0, 567, 561, 1, 0, 0, 0, 567, 562, 1, 0, 0, 0, 568, 83, 1, 0, 0, 0, 569, 574, 3, 86, 43, 0, 570, 571, 5, 112, 0, 0, 571, 573, 3, 86, 43, 0, 572, 570, 1, 0, 0, 0, 573, 576, 1, 0, 0, 0, 574, 572, 1, 0, 0, 0, 574, 575, 1, 0, 0, 0, 575, 85, 1, 0, 0, 0, 576, 574, 1, 0, 0, 0, 577, 578, 3, 150, 75, 0, 578, 579, 5, 118, 0, 0, 579, 580, 3, 140, 70, 0, 580, 87, 1, 0, 0, 0, 581, 583, 3, 90, 45, 0, 582, 581, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 585, 1, 0, 0, 0, 584, 586, 3, 92, 46, 0, 585, 584, 1, 0, 0, 0, 585, 586, 1, 0, 0, 0, 586, 588, 1, 0, 0, 0, 587, 589, 3, 94, 47, 0, 588, 587, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 89, 1, 0, 0, 0, 590, 591, 5, 65, 0, 0, 591, 592, 5, 11, 0, 0, 592, 593, 3, 104, 52, 0, 593, 91, 1, 0, 0, 0, 594, 595, 5, 62, 0, 0, 595, 596, 5, 11, 0, 0, 596, 597, 3, 78, 39, 0, 597, 93, 1, 0, 0, 0, 598, 599, 7, 8, 0, 0, 599, 600, 3, 96, 48, 0, 600, 95, 1, 0, 0, 0, 601, 608, 3, 98, 49, 0, 602, 603, 5, 9, 0, 0, 603, 604, 3, 98, 49, 0, 604, 605, 5, 2, 0, 0, 605, 606, 3, 98, 49, 0, 606, 608, 1, 0, 0, 0, 607, 601, 1, 0, 0, 0, 607, 602, 1, 0, 0, 0, 608, 97, 1, 0, 0, 0, 609, 610, 5, 18, 0, 0, 610, 622, 5, 73, 0, 0, 611, 612, 5, 90, 0, 0, 612, 622, 5, 66, 0, 0, 613, 614, 5, 90, 0, 0, 614, 622, 5, 30, 0, 0, 615, 616, 3, 138, 69, 0, 616, 617, 5, 66, 0, 0, 617, 622, 1, 0, 0, 0, 618, 619, 3, 138, 69, 0, 619, 620, 5, 30, 0, 0, 620, 622, 1, 0, 0, 0, 621, 609, 1, 0, 0, 0, 621, 611, 1, 0, 0, 0, 621, 613, 1, 0, 0, 0, 621, 615, 1, 0, 0, 0, 621, 618, 1, 0, 0, 0, 622, 99, 1, 0, 0, 0, 623, 624, 3, 106, 53, 0, 624, 625, 5, 0, 0, 1, 625, 101, 1, 0, 0, 0, 626, 674, 3, 150, 75, 0, 627, 628, 3, 150, 75, 0, 628, 629, 5, 126, 0, 0, 629, 630, 3, 150, 75, 0, 630, 637, 3, 102, 51, 0, 631, 632, 5, 112, 0, 0, 632, 633, 3, 150, 75, 0, 633, 634, 3, 102, 51, 0, 634, 636, 1, 0, 0, 0, 635, 631, 1, 0, 0, 0, 636, 639, 1, 0, 0, 0, 637, 635, 1, 0, 0, 0, 637, 638, 1, 0, 0, 0, 638, 640, 1, 0, 0, 0, 639, 637, 1, 0, 0, 0, 640, 641, 5, 144, 0, 0, 641, 674, 1, 0, 0, 0, 642, 643, 3, 150, 75, 0, 643, 644, 5, 126, 0, 0, 644, 649, 3, 152, 76, 0, 645, 646, 5, 112, 0, 0, 646, 648, 3, 152, 76, 0, 647, 645, 1, 0, 0, 0, 648, 651, 1, 0, 0, 0, 649, 647, 1, 0, 0, 0, 649, 650, 1, 0, 0, 0, 650, 652, 1, 0, 0, 0, 651, 649, 1, 0, 0, 0, 652, 653, 5, 144, 0, 0, 653, 674, 1, 0, 0, 0, 654, 655, 3, 150, 75, 0, 655, 656, 5, 126, 0, 0, 656, 661, 3, 102, 51, 0, 657, 658, 5, 112, 0, 0, 658, 660, 3, 102, 51, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 664, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 144, 0, 0, 665, 674, 1, 0, 0, 0, 666, 667, 3, 150, 75, 0, 667, 669, 5, 126, 0, 0, 668, 670, 3, 104, 52, 0, 669, 668, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 1, 0, 0, 0, 671, 672, 5, 144, 0, 0, 672, 674, 1, 0, 0, 0, 673, 626, 1, 0, 0, 0, 673, 627, 1, 0, 0, 0, 673, 642, 1, 0, 0, 0, 673, 654, 1, 0, 0, 0, 673, 666, 1, 0, 0, 0, 674, 103, 1, 0, 0, 0, 675, 680, 3, 106, 53, 0, 676, 677, 5, 112, 0, 0, 677, 679, 3, 106, 53, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 105, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 6, 53, -1, 0, 684, 686, 5, 12, 0, 0, 685, 687, 3, 106, 53, 0, 686, 685, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 693, 1, 0, 0, 0, 688, 689, 5, 94, 0, 0, 689, 690, 3, 106, 53, 0, 690, 691, 5, 81, 0, 0, 691, 692, 3, 106, 53, 0, 692, 694, 1, 0, 0, 0, 693, 688, 1, 0, 0, 0, 694, 695, 1, 0, 0, 0, 695, 693, 1, 0, 0, 0, 695, 696, 1, 0, 0, 0, 696, 699, 1, 0, 0, 0, 697, 698, 5, 24, 0, 0, 698, 700, 3, 106, 53, 0, 699, 697, 1, 0, 0, 0, 699, 700, 1, 0, 0, 0, 700, 701, 1, 0, 0, 0, 701, 702, 5, 25, 0, 0, 702, 813, 1, 0, 0, 0, 703, 704, 5, 13, 0, 0, 704, 705, 5, 126, 0, 0, 705, 706, 3, 106, 53, 0, 706, 707, 5, 6, 0, 0, 707, 708, 3, 102, 51, 0, 708, 709, 5, 144, 0, 0, 709, 813, 1, 0, 0, 0, 710, 711, 5, 19, 0, 0, 711, 813, 5, 106, 0, 0, 712, 713, 5, 43, 0, 0, 713, 714, 3, 106, 53, 0, 714, 715, 3, 142, 71, 0, 715, 813, 1, 0, 0, 0, 716, 717, 5, 80, 0, 0, 717, 718, 5, 126, 0, 0, 718, 719, 3, 106, 53, 0, 719, 720, 5, 32, 0, 0, 720, 723, 3, 106, 53, 0, 721, 722, 5, 31, 0, 0, 722, 724, 3, 106, 53, 0, 723, 721, 1, 0, 0, 0, 723, 724, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 726, 5, 144, 0, 0, 726, 813, 1, 0, 0, 0, 727, 728, 5, 83, 0, 0, 728, 813, 5, 106, 0, 0, 729, 730, 5, 88, 0, 0, 730, 731, 5, 126, 0, 0, 731, 732, 7, 9, 0, 0, 732, 733, 3, 156, 78, 0, 733, 734, 5, 32, 0, 0, 734, 735, 3, 106, 53, 0, 735, 736, 5, 144, 0, 0, 736, 813, 1, 0, 0, 0, 737, 738, 3, 150, 75, 0, 738, 740, 5, 126, 0, 0, 739, 741, 3, 104, 52, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 144, 0, 0, 743, 744, 1, 0, 0, 0, 744, 745, 5, 64, 0, 0, 745, 746, 5, 126, 0, 0, 746, 747, 3, 88, 44, 0, 747, 748, 5, 144, 0, 0, 748, 813, 1, 0, 0, 0, 749, 750, 3, 150, 75, 0, 750, 752, 5, 126, 0, 0, 751, 753, 3, 104, 52, 0, 752, 751, 1, 0, 0, 0, 752, 753, 1, 0, 0, 0, 753, 754, 1, 0, 0, 0, 754, 755, 5, 144, 0, 0, 755, 756, 1, 0, 0, 0, 756, 757, 5, 64, 0, 0, 757, 758, 3, 150, 75, 0, 758, 813, 1, 0, 0, 0, 759, 765, 3, 150, 75, 0, 760, 762, 5, 126, 0, 0, 761, 763, 3, 104, 52, 0, 762, 761, 1, 0, 0, 0, 762, 763, 1, 0, 0, 0, 763, 764, 1, 0, 0, 0, 764, 766, 5, 144, 0, 0, 765, 760, 1, 0, 0, 0, 765, 766, 1, 0, 0, 0, 766, 767, 1, 0, 0, 0, 767, 769, 5, 126, 0, 0, 768, 770, 5, 23, 0, 0, 769, 768, 1, 0, 0, 0, 769, 770, 1, 0, 0, 0, 770, 772, 1, 0, 0, 0, 771, 773, 3, 108, 54, 0, 772, 771, 1, 0, 0, 0, 772, 773, 1, 0, 0, 0, 773, 774, 1, 0, 0, 0, 774, 775, 5, 144, 0, 0, 775, 813, 1, 0, 0, 0, 776, 813, 3, 114, 57, 0, 777, 813, 3, 158, 79, 0, 778, 813, 3, 140, 70, 0, 779, 780, 5, 114, 0, 0, 780, 813, 3, 106, 53, 19, 781, 782, 5, 56, 0, 0, 782, 813, 3, 106, 53, 13, 783, 784, 3, 130, 65, 0, 784, 785, 5, 116, 0, 0, 785, 787, 1, 0, 0, 0, 786, 783, 1, 0, 0, 0, 786, 787, 1, 0, 0, 0, 787, 788, 1, 0, 0, 0, 788, 813, 5, 108, 0, 0, 789, 790, 5, 126, 0, 0, 790, 791, 3, 34, 17, 0, 791, 792, 5, 144, 0, 0, 792, 813, 1, 0, 0, 0, 793, 794, 5, 126, 0, 0, 794, 795, 3, 106, 53, 0, 795, 796, 5, 144, 0, 0, 796, 813, 1, 0, 0, 0, 797, 798, 5, 126, 0, 0, 798, 799, 3, 104, 52, 0, 799, 800, 5, 144, 0, 0, 800, 813, 1, 0, 0, 0, 801, 803, 5, 125, 0, 0, 802, 804, 3, 104, 52, 0, 803, 802, 1, 0, 0, 0, 803, 804, 1, 0, 0, 0, 804, 805, 1, 0, 0, 0, 805, 813, 5, 143, 0, 0, 806, 808, 5, 124, 0, 0, 807, 809, 3, 30, 15, 0, 808, 807, 1, 0, 0, 0, 808, 809, 1, 0, 0, 0, 809, 810, 1, 0, 0, 0, 810, 813, 5, 142, 0, 0, 811, 813, 3, 122, 61, 0, 812, 683, 1, 0, 0, 0, 812, 703, 1, 0, 0, 0, 812, 710, 1, 0, 0, 0, 812, 712, 1, 0, 0, 0, 812, 716, 1, 0, 0, 0, 812, 727, 1, 0, 0, 0, 812, 729, 1, 0, 0, 0, 812, 737, 1, 0, 0, 0, 812, 749, 1, 0, 0, 0, 812, 759, 1, 0, 0, 0, 812, 776, 1, 0, 0, 0, 812, 777, 1, 0, 0, 0, 812, 778, 1, 0, 0, 0, 812, 779, 1, 0, 0, 0, 812, 781, 1, 0, 0, 0, 812, 786, 1, 0, 0, 0, 812, 789, 1, 0, 0, 0, 812, 793, 1, 0, 0, 0, 812, 797, 1, 0, 0, 0, 812, 801, 1, 0, 0, 0, 812, 806, 1, 0, 0, 0, 812, 811, 1, 0, 0, 0, 813, 907, 1, 0, 0, 0, 814, 818, 10, 18, 0, 0, 815, 819, 5, 108, 0, 0, 816, 819, 5, 146, 0, 0, 817, 819, 5, 133, 0, 0, 818, 815, 1, 0, 0, 0, 818, 816, 1, 0, 0, 0, 818, 817, 1, 0, 0, 0, 819, 820, 1, 0, 0, 0, 820, 906, 3, 106, 53, 19, 821, 825, 10, 17, 0, 0, 822, 826, 5, 134, 0, 0, 823, 826, 5, 114, 0, 0, 824, 826, 5, 113, 0, 0, 825, 822, 1, 0, 0, 0, 825, 823, 1, 0, 0, 0, 825, 824, 1, 0, 0, 0, 826, 827, 1, 0, 0, 0, 827, 906, 3, 106, 53, 18, 828, 853, 10, 16, 0, 0, 829, 854, 5, 117, 0, 0, 830, 854, 5, 118, 0, 0, 831, 854, 5, 129, 0, 0, 832, 854, 5, 127, 0, 0, 833, 854, 5, 128, 0, 0, 834, 854, 5, 119, 0, 0, 835, 854, 5, 120, 0, 0, 836, 838, 5, 56, 0, 0, 837, 836, 1, 0, 0, 0, 837, 838, 1, 0, 0, 0, 838, 839, 1, 0, 0, 0, 839, 841, 5, 40, 0, 0, 840, 842, 5, 14, 0, 0, 841, 840, 1, 0, 0, 0, 841, 842, 1, 0, 0, 0, 842, 854, 1, 0, 0, 0, 843, 845, 5, 56, 0, 0, 844, 843, 1, 0, 0, 0, 844, 845, 1, 0, 0, 0, 845, 846, 1, 0, 0, 0, 846, 854, 7, 10, 0, 0, 847, 854, 5, 140, 0, 0, 848, 854, 5, 141, 0, 0, 849, 854, 5, 131, 0, 0, 850, 854, 5, 122, 0, 0, 851, 854, 5, 123, 0, 0, 852, 854, 5, 130, 0, 0, 853, 829, 1, 0, 0, 0, 853, 830, 1, 0, 0, 0, 853, 831, 1, 0, 0, 0, 853, 832, 1, 0, 0, 0, 853, 833, 1, 0, 0, 0, 853, 834, 1, 0, 0, 0, 853, 835, 1, 0, 0, 0, 853, 837, 1, 0, 0, 0, 853, 844, 1, 0, 0, 0, 853, 847, 1, 0, 0, 0, 853, 848, 1, 0, 0, 0, 853, 849, 1, 0, 0, 0, 853, 850, 1, 0, 0, 0, 853, 851, 1, 0, 0, 0, 853, 852, 1, 0, 0, 0, 854, 855, 1, 0, 0, 0, 855, 906, 3, 106, 53, 17, 856, 857, 10, 14, 0, 0, 857, 858, 5, 132, 0, 0, 858, 906, 3, 106, 53, 15, 859, 860, 10, 12, 0, 0, 860, 861, 5, 2, 0, 0, 861, 906, 3, 106, 53, 13, 862, 863, 10, 11, 0, 0, 863, 864, 5, 61, 0, 0, 864, 906, 3, 106, 53, 12, 865, 867, 10, 10, 0, 0, 866, 868, 5, 56, 0, 0, 867, 866, 1, 0, 0, 0, 867, 868, 1, 0, 0, 0, 868, 869, 1, 0, 0, 0, 869, 870, 5, 9, 0, 0, 870, 871, 3, 106, 53, 0, 871, 872, 5, 2, 0, 0, 872, 873, 3, 106, 53, 11, 873, 906, 1, 0, 0, 0, 874, 875, 10, 9, 0, 0, 875, 876, 5, 135, 0, 0, 876, 877, 3, 106, 53, 0, 877, 878, 5, 111, 0, 0, 878, 879, 3, 106, 53, 9, 879, 906, 1, 0, 0, 0, 880, 881, 10, 22, 0, 0, 881, 882, 5, 125, 0, 0, 882, 883, 3, 106, 53, 0, 883, 884, 5, 143, 0, 0, 884, 906, 1, 0, 0, 0, 885, 886, 10, 21, 0, 0, 886, 887, 5, 116, 0, 0, 887, 906, 5, 104, 0, 0, 888, 889, 10, 20, 0, 0, 889, 890, 5, 116, 0, 0, 890, 906, 3, 150, 75, 0, 891, 892, 10, 15, 0, 0, 892, 894, 5, 44, 0, 0, 893, 895, 5, 56, 0, 0, 894, 893, 1, 0, 0, 0, 894, 895, 1, 0, 0, 0, 895, 896, 1, 0, 0, 0, 896, 906, 5, 57, 0, 0, 897, 903, 10, 8, 0, 0, 898, 904, 3, 148, 74, 0, 899, 900, 5, 6, 0, 0, 900, 904, 3, 150, 75, 0, 901, 902, 5, 6, 0, 0, 902, 904, 5, 106, 0, 0, 903, 898, 1, 0, 0, 0, 903, 899, 1, 0, 0, 0, 903, 901, 1, 0, 0, 0, 904, 906, 1, 0, 0, 0, 905, 814, 1, 0, 0, 0, 905, 821, 1, 0, 0, 0, 905, 828, 1, 0, 0, 0, 905, 856, 1, 0, 0, 0, 905, 859, 1, 0, 0, 0, 905, 862, 1, 0, 0, 0, 905, 865, 1, 0, 0, 0, 905, 874, 1, 0, 0, 0, 905, 880, 1, 0, 0, 0, 905, 885, 1, 0, 0, 0, 905, 888, 1, 0, 0, 0, 905, 891, 1, 0, 0, 0, 905, 897, 1, 0, 0, 0, 906, 909, 1, 0, 0, 0, 907, 905, 1, 0, 0, 0, 907, 908, 1, 0, 0, 0, 908, 107, 1, 0, 0, 0, 909, 907, 1, 0, 0, 0, 910, 915, 3, 110, 55, 0, 911, 912, 5, 112, 0, 0, 912, 914, 3, 110, 55, 0, 913, 911, 1, 0, 0, 0, 914, 917, 1, 0, 0, 0, 915, 913, 1, 0, 0, 0, 915, 916, 1, 0, 0, 0, 916, 109, 1, 0, 0, 0, 917, 915, 1, 0, 0, 0, 918, 921, 3, 112, 56, 0, 919, 921, 3, 106, 53, 0, 920, 918, 1, 0, 0, 0, 920, 919, 1, 0, 0, 0, 921, 111, 1, 0, 0, 0, 922, 923, 5, 126, 0, 0, 923, 928, 3, 150, 75, 0, 924, 925, 5, 112, 0, 0, 925, 927, 3, 150, 75, 0, 926, 924, 1, 0, 0, 0, 927, 930, 1, 0, 0, 0, 928, 926, 1, 0, 0, 0, 928, 929, 1, 0, 0, 0, 929, 931, 1, 0, 0, 0, 930, 928, 1, 0, 0, 0, 931, 932, 5, 144, 0, 0, 932, 942, 1, 0, 0, 0, 933, 938, 3, 150, 75, 0, 934, 935, 5, 112, 0, 0, 935, 937, 3, 150, 75, 0, 936, 934, 1, 0, 0, 0, 937, 940, 1, 0, 0, 0, 938, 936, 1, 0, 0, 0, 938, 939, 1, 0, 0, 0, 939, 942, 1, 0, 0, 0, 940, 938, 1, 0, 0, 0, 941, 922, 1, 0, 0, 0, 941, 933, 1, 0, 0, 0, 942, 943, 1, 0, 0, 0, 943, 944, 5, 107, 0, 0, 944, 945, 3, 106, 53, 0, 945, 113, 1, 0, 0, 0, 946, 947, 5, 128, 0, 0, 947, 951, 3, 150, 75, 0, 948, 950, 3, 116, 58, 0, 949, 948, 1, 0, 0, 0, 950, 953, 1, 0, 0, 0, 951, 949, 1, 0, 0, 0, 951, 952, 1, 0, 0, 0, 952, 954, 1, 0, 0, 0, 953, 951, 1, 0, 0, 0, 954, 955, 5, 146, 0, 0, 955, 956, 5, 120, 0, 0, 956, 975, 1, 0, 0, 0, 957, 958, 5, 128, 0, 0, 958, 962, 3, 150, 75, 0, 959, 961, 3, 116, 58, 0, 960, 959, 1, 0, 0, 0, 961, 964, 1, 0, 0, 0, 962, 960, 1, 0, 0, 0, 962, 963, 1, 0, 0, 0, 963, 965, 1, 0, 0, 0, 964, 962, 1, 0, 0, 0, 965, 967, 5, 120, 0, 0, 966, 968, 3, 114, 57, 0, 967, 966, 1, 0, 0, 0, 967, 968, 1, 0, 0, 0, 968, 969, 1, 0, 0, 0, 969, 970, 5, 128, 0, 0, 970, 971, 5, 146, 0, 0, 971, 972, 3, 150, 75, 0, 972, 973, 5, 120, 0, 0, 973, 975, 1, 0, 0, 0, 974, 946, 1, 0, 0, 0, 974, 957, 1, 0, 0, 0, 975, 115, 1, 0, 0, 0, 976, 977, 3, 150, 75, 0, 977, 978, 5, 118, 0, 0, 978, 979, 3, 156, 78, 0, 979, 988, 1, 0, 0, 0, 980, 981, 3, 150, 75, 0, 981, 982, 5, 118, 0, 0, 982, 983, 5, 124, 0, 0, 983, 984, 3, 106, 53, 0, 984, 985, 5, 142, 0, 0, 985, 988, 1, 0, 0, 0, 986, 988, 3, 150, 75, 0, 987, 976, 1, 0, 0, 0, 987, 980, 1, 0, 0, 0, 987, 986, 1, 0, 0, 0, 988, 117, 1, 0, 0, 0, 989, 994, 3, 120, 60, 0, 990, 991, 5, 112, 0, 0, 991, 993, 3, 120, 60, 0, 992, 990, 1, 0, 0, 0, 993, 996, 1, 0, 0, 0, 994, 992, 1, 0, 0, 0, 994, 995, 1, 0, 0, 0, 995, 119, 1, 0, 0, 0, 996, 994, 1, 0, 0, 0, 997, 998, 3, 150, 75, 0, 998, 999, 5, 6, 0, 0, 999, 1000, 5, 126, 0, 0, 1000, 1001, 3, 34, 17, 0, 1001, 1002, 5, 144, 0, 0, 1002, 1008, 1, 0, 0, 0, 1003, 1004, 3, 106, 53, 0, 1004, 1005, 5, 6, 0, 0, 1005, 1006, 3, 150, 75, 0, 1006, 1008, 1, 0, 0, 0, 1007, 997, 1, 0, 0, 0, 1007, 1003, 1, 0, 0, 0, 1008, 121, 1, 0, 0, 0, 1009, 1017, 3, 154, 77, 0, 1010, 1011, 3, 130, 65, 0, 1011, 1012, 5, 116, 0, 0, 1012, 1014, 1, 0, 0, 0, 1013, 1010, 1, 0, 0, 0, 1013, 1014, 1, 0, 0, 0, 1014, 1015, 1, 0, 0, 0, 1015, 1017, 3, 124, 62, 0, 1016, 1009, 1, 0, 0, 0, 1016, 1013, 1, 0, 0, 0, 1017, 123, 1, 0, 0, 0, 1018, 1023, 3, 150, 75, 0, 1019, 1020, 5, 116, 0, 0, 1020, 1022, 3, 150, 75, 0, 1021, 1019, 1, 0, 0, 0, 1022, 1025, 1, 0, 0, 0, 1023, 1021, 1, 0, 0, 0, 1023, 1024, 1, 0, 0, 0, 1024, 125, 1, 0, 0, 0, 1025, 1023, 1, 0, 0, 0, 1026, 1027, 6, 63, -1, 0, 1027, 1036, 3, 130, 65, 0, 1028, 1036, 3, 128, 64, 0, 1029, 1030, 5, 126, 0, 0, 1030, 1031, 3, 34, 17, 0, 1031, 1032, 5, 144, 0, 0, 1032, 1036, 1, 0, 0, 0, 1033, 1036, 3, 114, 57, 0, 1034, 1036, 3, 154, 77, 0, 1035, 1026, 1, 0, 0, 0, 1035, 1028, 1, 0, 0, 0, 1035, 1029, 1, 0, 0, 0, 1035, 1033, 1, 0, 0, 0, 1035, 1034, 1, 0, 0, 0, 1036, 1045, 1, 0, 0, 0, 1037, 1041, 10, 3, 0, 0, 1038, 1042, 3, 148, 74, 0, 1039, 1040, 5, 6, 0, 0, 1040, 1042, 3, 150, 75, 0, 1041, 1038, 1, 0, 0, 0, 1041, 1039, 1, 0, 0, 0, 1042, 1044, 1, 0, 0, 0, 1043, 1037, 1, 0, 0, 0, 1044, 1047, 1, 0, 0, 0, 1045, 1043, 1, 0, 0, 0, 1045, 1046, 1, 0, 0, 0, 1046, 127, 1, 0, 0, 0, 1047, 1045, 1, 0, 0, 0, 1048, 1049, 3, 150, 75, 0, 1049, 1051, 5, 126, 0, 0, 1050, 1052, 3, 132, 66, 0, 1051, 1050, 1, 0, 0, 0, 1051, 1052, 1, 0, 0, 0, 1052, 1053, 1, 0, 0, 0, 1053, 1054, 5, 144, 0, 0, 1054, 129, 1, 0, 0, 0, 1055, 1056, 3, 134, 67, 0, 1056, 1057, 5, 116, 0, 0, 1057, 1059, 1, 0, 0, 0, 1058, 1055, 1, 0, 0, 0, 1058, 1059, 1, 0, 0, 0, 1059, 1060, 1, 0, 0, 0, 1060, 1061, 3, 150, 75, 0, 1061, 131, 1, 0, 0, 0, 1062, 1067, 3, 106, 53, 0, 1063, 1064, 5, 112, 0, 0, 1064, 1066, 3, 106, 53, 0, 1065, 1063, 1, 0, 0, 0, 1066, 1069, 1, 0, 0, 0, 1067, 1065, 1, 0, 0, 0, 1067, 1068, 1, 0, 0, 0, 1068, 133, 1, 0, 0, 0, 1069, 1067, 1, 0, 0, 0, 1070, 1071, 3, 150, 75, 0, 1071, 135, 1, 0, 0, 0, 1072, 1081, 5, 102, 0, 0, 1073, 1074, 5, 116, 0, 0, 1074, 1081, 7, 11, 0, 0, 1075, 1076, 5, 104, 0, 0, 1076, 1078, 5, 116, 0, 0, 1077, 1079, 7, 11, 0, 0, 1078, 1077, 1, 0, 0, 0, 1078, 1079, 1, 0, 0, 0, 1079, 1081, 1, 0, 0, 0, 1080, 1072, 1, 0, 0, 0, 1080, 1073, 1, 0, 0, 0, 1080, 1075, 1, 0, 0, 0, 1081, 137, 1, 0, 0, 0, 1082, 1084, 7, 12, 0, 0, 1083, 1082, 1, 0, 0, 0, 1083, 1084, 1, 0, 0, 0, 1084, 1091, 1, 0, 0, 0, 1085, 1092, 3, 136, 68, 0, 1086, 1092, 5, 103, 0, 0, 1087, 1092, 5, 104, 0, 0, 1088, 1092, 5, 105, 0, 0, 1089, 1092, 5, 41, 0, 0, 1090, 1092, 5, 55, 0, 0, 1091, 1085, 1, 0, 0, 0, 1091, 1086, 1, 0, 0, 0, 1091, 1087, 1, 0, 0, 0, 1091, 1088, 1, 0, 0, 0, 1091, 1089, 1, 0, 0, 0, 1091, 1090, 1, 0, 0, 0, 1092, 139, 1, 0, 0, 0, 1093, 1097, 3, 138, 69, 0, 1094, 1097, 5, 106, 0, 0, 1095, 1097, 5, 57, 0, 0, 1096, 1093, 1, 0, 0, 0, 1096, 1094, 1, 0, 0, 0, 1096, 1095, 1, 0, 0, 0, 1097, 141, 1, 0, 0, 0, 1098, 1099, 7, 13, 0, 0, 1099, 143, 1, 0, 0, 0, 1100, 1101, 7, 14, 0, 0, 1101, 145, 1, 0, 0, 0, 1102, 1103, 7, 15, 0, 0, 1103, 147, 1, 0, 0, 0, 1104, 1107, 5, 101, 0, 0, 1105, 1107, 3, 146, 73, 0, 1106, 1104, 1, 0, 0, 0, 1106, 1105, 1, 0, 0, 0, 1107, 149, 1, 0, 0, 0, 1108, 1112, 5, 101, 0, 0, 1109, 1112, 3, 142, 71, 0, 1110, 1112, 3, 144, 72, 0, 1111, 1108, 1, 0, 0, 0, 1111, 1109, 1, 0, 0, 0, 1111, 1110, 1, 0, 0, 0, 1112, 151, 1, 0, 0, 0, 1113, 1114, 3, 156, 78, 0, 1114, 1115, 5, 118, 0, 0, 1115, 1116, 3, 138, 69, 0, 1116, 153, 1, 0, 0, 0, 1117, 1118, 5, 124, 0, 0, 1118, 1119, 3, 150, 75, 0, 1119, 1120, 5, 142, 0, 0, 1120, 155, 1, 0, 0, 0, 1121, 1124, 5, 106, 0, 0, 1122, 1124, 3, 158, 79, 0, 1123, 1121, 1, 0, 0, 0, 1123, 1122, 1, 0, 0, 0, 1124, 157, 1, 0, 0, 0, 1125, 1129, 5, 137, 0, 0, 1126, 1128, 3, 160, 80, 0, 1127, 1126, 1, 0, 0, 0, 1128, 1131, 1, 0, 0, 0, 1129, 1127, 1, 0, 0, 0, 1129, 1130, 1, 0, 0, 0, 1130, 1132, 1, 0, 0, 0, 1131, 1129, 1, 0, 0, 0, 1132, 1133, 5, 139, 0, 0, 1133, 159, 1, 0, 0, 0, 1134, 1135, 5, 152, 0, 0, 1135, 1136, 3, 106, 53, 0, 1136, 1137, 5, 142, 0, 0, 1137, 1140, 1, 0, 0, 0, 1138, 1140, 5, 151, 0, 0, 1139, 1134, 1, 0, 0, 0, 1139, 1138, 1, 0, 0, 0, 1140, 161, 1, 0, 0, 0, 1141, 1145, 5, 138, 0, 0, 1142, 1144, 3, 164, 82, 0, 1143, 1142, 1, 0, 0, 0, 1144, 1147, 1, 0, 0, 0, 1145, 1143, 1, 0, 0, 0, 1145, 1146, 1, 0, 0, 0, 1146, 1148, 1, 0, 0, 0, 1147, 1145, 1, 0, 0, 0, 1148, 1149, 5, 0, 0, 1, 1149, 163, 1, 0, 0, 0, 1150, 1151, 5, 154, 0, 0, 1151, 1152, 3, 106, 53, 0, 1152, 1153, 5, 142, 0, 0, 1153, 1156, 1, 0, 0, 0, 1154, 1156, 5, 153, 0, 0, 1155, 1150, 1, 0, 0, 0, 1155, 1154, 1, 0, 0, 0, 1156, 165, 1, 0, 0, 0, 135, 169, 176, 185, 200, 212, 224, 240, 251, 265, 271, 281, 290, 293, 297, 300, 304, 307, 310, 313, 316, 320, 324, 327, 330, 333, 337, 340, 349, 355, 376, 393, 410, 416, 422, 433, 435, 446, 449, 455, 463, 469, 471, 475, 480, 483, 486, 490, 494, 497, 499, 502, 506, 510, 513, 515, 517, 522, 533, 539, 546, 551, 555, 559, 565, 567, 574, 582, 585, 588, 607, 621, 637, 649, 661, 669, 673, 680, 686, 695, 699, 723, 740, 752, 762, 765, 769, 772, 786, 803, 808, 812, 818, 825, 837, 841, 844, 853, 867, 894, 903, 905, 907, 915, 920, 928, 938, 941, 951, 962, 967, 974, 987, 994, 1007, 1013, 1016, 1023, 1035, 1041, 1045, 1051, 1058, 1067, 1078, 1080, 1083, 1091, 1096, 1106, 1111, 1123, 1129, 1139, 1145, 1155] \ No newline at end of file +[4, 1, 154, 1178, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 1, 0, 5, 0, 168, 8, 0, 10, 0, 12, 0, 171, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 177, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 186, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 5, 5, 199, 8, 5, 10, 5, 12, 5, 202, 9, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 213, 8, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 225, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 241, 8, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 5, 13, 250, 8, 13, 10, 13, 12, 13, 253, 9, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 5, 15, 264, 8, 15, 10, 15, 12, 15, 267, 9, 15, 1, 16, 1, 16, 1, 16, 3, 16, 272, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 5, 17, 280, 8, 17, 10, 17, 12, 17, 283, 9, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 3, 18, 291, 8, 18, 1, 19, 3, 19, 294, 8, 19, 1, 19, 1, 19, 3, 19, 298, 8, 19, 1, 19, 3, 19, 301, 8, 19, 1, 19, 1, 19, 3, 19, 305, 8, 19, 1, 19, 3, 19, 308, 8, 19, 1, 19, 3, 19, 311, 8, 19, 1, 19, 3, 19, 314, 8, 19, 1, 19, 3, 19, 317, 8, 19, 1, 19, 1, 19, 3, 19, 321, 8, 19, 1, 19, 1, 19, 3, 19, 325, 8, 19, 1, 19, 3, 19, 328, 8, 19, 1, 19, 3, 19, 331, 8, 19, 1, 19, 3, 19, 334, 8, 19, 1, 19, 1, 19, 3, 19, 338, 8, 19, 1, 19, 3, 19, 341, 8, 19, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 350, 8, 21, 1, 22, 1, 22, 1, 22, 1, 23, 3, 23, 356, 8, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 375, 8, 24, 10, 24, 12, 24, 378, 9, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 3, 27, 394, 8, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 411, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 417, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 423, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 434, 8, 31, 3, 31, 436, 8, 31, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 3, 34, 447, 8, 34, 1, 34, 3, 34, 450, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 456, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 464, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 470, 8, 34, 10, 34, 12, 34, 473, 9, 34, 1, 35, 3, 35, 476, 8, 35, 1, 35, 1, 35, 1, 35, 3, 35, 481, 8, 35, 1, 35, 3, 35, 484, 8, 35, 1, 35, 3, 35, 487, 8, 35, 1, 35, 1, 35, 3, 35, 491, 8, 35, 1, 35, 1, 35, 3, 35, 495, 8, 35, 1, 35, 3, 35, 498, 8, 35, 3, 35, 500, 8, 35, 1, 35, 3, 35, 503, 8, 35, 1, 35, 1, 35, 3, 35, 507, 8, 35, 1, 35, 1, 35, 3, 35, 511, 8, 35, 1, 35, 3, 35, 514, 8, 35, 3, 35, 516, 8, 35, 3, 35, 518, 8, 35, 1, 36, 1, 36, 1, 36, 3, 36, 523, 8, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 3, 37, 534, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 540, 8, 38, 1, 39, 1, 39, 1, 39, 5, 39, 545, 8, 39, 10, 39, 12, 39, 548, 9, 39, 1, 40, 1, 40, 3, 40, 552, 8, 40, 1, 40, 1, 40, 3, 40, 556, 8, 40, 1, 40, 1, 40, 3, 40, 560, 8, 40, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 566, 8, 41, 3, 41, 568, 8, 41, 1, 42, 1, 42, 1, 42, 5, 42, 573, 8, 42, 10, 42, 12, 42, 576, 9, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 3, 44, 583, 8, 44, 1, 44, 3, 44, 586, 8, 44, 1, 44, 3, 44, 589, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 3, 48, 608, 8, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 622, 8, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 636, 8, 51, 10, 51, 12, 51, 639, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 648, 8, 51, 10, 51, 12, 51, 651, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 660, 8, 51, 10, 51, 12, 51, 663, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, 670, 8, 51, 1, 51, 1, 51, 3, 51, 674, 8, 51, 1, 52, 1, 52, 1, 52, 5, 52, 679, 8, 52, 10, 52, 12, 52, 682, 9, 52, 1, 53, 1, 53, 1, 53, 3, 53, 687, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 4, 53, 694, 8, 53, 11, 53, 12, 53, 695, 1, 53, 1, 53, 3, 53, 700, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 724, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 741, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 747, 8, 53, 1, 53, 3, 53, 750, 8, 53, 1, 53, 3, 53, 753, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 763, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 769, 8, 53, 1, 53, 3, 53, 772, 8, 53, 1, 53, 3, 53, 775, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 783, 8, 53, 1, 53, 3, 53, 786, 8, 53, 1, 53, 1, 53, 3, 53, 790, 8, 53, 1, 53, 3, 53, 793, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 807, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 824, 8, 53, 1, 53, 1, 53, 1, 53, 3, 53, 829, 8, 53, 1, 53, 1, 53, 3, 53, 833, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 839, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 846, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 858, 8, 53, 1, 53, 1, 53, 3, 53, 862, 8, 53, 1, 53, 3, 53, 865, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 874, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 888, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 915, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 924, 8, 53, 5, 53, 926, 8, 53, 10, 53, 12, 53, 929, 9, 53, 1, 54, 1, 54, 1, 54, 5, 54, 934, 8, 54, 10, 54, 12, 54, 937, 9, 54, 1, 55, 1, 55, 3, 55, 941, 8, 55, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 947, 8, 56, 10, 56, 12, 56, 950, 9, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 957, 8, 56, 10, 56, 12, 56, 960, 9, 56, 3, 56, 962, 8, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 5, 57, 970, 8, 57, 10, 57, 12, 57, 973, 9, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 5, 57, 981, 8, 57, 10, 57, 12, 57, 984, 9, 57, 1, 57, 1, 57, 3, 57, 988, 8, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 3, 57, 995, 8, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 1008, 8, 58, 1, 59, 1, 59, 1, 59, 5, 59, 1013, 8, 59, 10, 59, 12, 59, 1016, 9, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 1028, 8, 60, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 1034, 8, 61, 1, 61, 3, 61, 1037, 8, 61, 1, 62, 1, 62, 1, 62, 5, 62, 1042, 8, 62, 10, 62, 12, 62, 1045, 9, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1056, 8, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1062, 8, 63, 5, 63, 1064, 8, 63, 10, 63, 12, 63, 1067, 9, 63, 1, 64, 1, 64, 1, 64, 3, 64, 1072, 8, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 3, 65, 1079, 8, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 5, 66, 1086, 8, 66, 10, 66, 12, 66, 1089, 9, 66, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 3, 68, 1099, 8, 68, 3, 68, 1101, 8, 68, 1, 69, 3, 69, 1104, 8, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 3, 69, 1112, 8, 69, 1, 70, 1, 70, 1, 70, 3, 70, 1117, 8, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 3, 74, 1127, 8, 74, 1, 75, 1, 75, 1, 75, 3, 75, 1132, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 3, 78, 1144, 8, 78, 1, 79, 1, 79, 5, 79, 1148, 8, 79, 10, 79, 12, 79, 1151, 9, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 3, 80, 1160, 8, 80, 1, 81, 1, 81, 5, 81, 1164, 8, 81, 10, 81, 12, 81, 1167, 9, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 3, 82, 1176, 8, 82, 1, 82, 0, 3, 68, 106, 126, 83, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 0, 16, 2, 0, 17, 17, 72, 72, 2, 0, 42, 42, 49, 49, 3, 0, 1, 1, 4, 4, 8, 8, 4, 0, 1, 1, 3, 4, 8, 8, 78, 78, 2, 0, 49, 49, 71, 71, 2, 0, 1, 1, 4, 4, 2, 0, 7, 7, 21, 22, 2, 0, 28, 28, 47, 47, 2, 0, 69, 69, 74, 74, 3, 0, 10, 10, 48, 48, 87, 87, 2, 0, 39, 39, 51, 51, 1, 0, 103, 104, 2, 0, 114, 114, 134, 134, 7, 0, 20, 20, 36, 36, 53, 54, 68, 68, 76, 76, 93, 93, 99, 99, 12, 0, 1, 19, 21, 28, 30, 35, 37, 40, 42, 49, 51, 52, 56, 56, 58, 67, 69, 75, 77, 92, 94, 95, 97, 98, 4, 0, 19, 19, 28, 28, 37, 37, 46, 46, 1314, 0, 169, 1, 0, 0, 0, 2, 176, 1, 0, 0, 0, 4, 178, 1, 0, 0, 0, 6, 180, 1, 0, 0, 0, 8, 189, 1, 0, 0, 0, 10, 195, 1, 0, 0, 0, 12, 212, 1, 0, 0, 0, 14, 214, 1, 0, 0, 0, 16, 217, 1, 0, 0, 0, 18, 226, 1, 0, 0, 0, 20, 232, 1, 0, 0, 0, 22, 236, 1, 0, 0, 0, 24, 245, 1, 0, 0, 0, 26, 247, 1, 0, 0, 0, 28, 256, 1, 0, 0, 0, 30, 260, 1, 0, 0, 0, 32, 271, 1, 0, 0, 0, 34, 275, 1, 0, 0, 0, 36, 290, 1, 0, 0, 0, 38, 293, 1, 0, 0, 0, 40, 342, 1, 0, 0, 0, 42, 345, 1, 0, 0, 0, 44, 351, 1, 0, 0, 0, 46, 355, 1, 0, 0, 0, 48, 361, 1, 0, 0, 0, 50, 379, 1, 0, 0, 0, 52, 382, 1, 0, 0, 0, 54, 385, 1, 0, 0, 0, 56, 395, 1, 0, 0, 0, 58, 398, 1, 0, 0, 0, 60, 402, 1, 0, 0, 0, 62, 435, 1, 0, 0, 0, 64, 437, 1, 0, 0, 0, 66, 440, 1, 0, 0, 0, 68, 455, 1, 0, 0, 0, 70, 517, 1, 0, 0, 0, 72, 522, 1, 0, 0, 0, 74, 533, 1, 0, 0, 0, 76, 535, 1, 0, 0, 0, 78, 541, 1, 0, 0, 0, 80, 549, 1, 0, 0, 0, 82, 567, 1, 0, 0, 0, 84, 569, 1, 0, 0, 0, 86, 577, 1, 0, 0, 0, 88, 582, 1, 0, 0, 0, 90, 590, 1, 0, 0, 0, 92, 594, 1, 0, 0, 0, 94, 598, 1, 0, 0, 0, 96, 607, 1, 0, 0, 0, 98, 621, 1, 0, 0, 0, 100, 623, 1, 0, 0, 0, 102, 673, 1, 0, 0, 0, 104, 675, 1, 0, 0, 0, 106, 832, 1, 0, 0, 0, 108, 930, 1, 0, 0, 0, 110, 940, 1, 0, 0, 0, 112, 961, 1, 0, 0, 0, 114, 994, 1, 0, 0, 0, 116, 1007, 1, 0, 0, 0, 118, 1009, 1, 0, 0, 0, 120, 1027, 1, 0, 0, 0, 122, 1036, 1, 0, 0, 0, 124, 1038, 1, 0, 0, 0, 126, 1055, 1, 0, 0, 0, 128, 1068, 1, 0, 0, 0, 130, 1078, 1, 0, 0, 0, 132, 1082, 1, 0, 0, 0, 134, 1090, 1, 0, 0, 0, 136, 1100, 1, 0, 0, 0, 138, 1103, 1, 0, 0, 0, 140, 1116, 1, 0, 0, 0, 142, 1118, 1, 0, 0, 0, 144, 1120, 1, 0, 0, 0, 146, 1122, 1, 0, 0, 0, 148, 1126, 1, 0, 0, 0, 150, 1131, 1, 0, 0, 0, 152, 1133, 1, 0, 0, 0, 154, 1137, 1, 0, 0, 0, 156, 1143, 1, 0, 0, 0, 158, 1145, 1, 0, 0, 0, 160, 1159, 1, 0, 0, 0, 162, 1161, 1, 0, 0, 0, 164, 1175, 1, 0, 0, 0, 166, 168, 3, 2, 1, 0, 167, 166, 1, 0, 0, 0, 168, 171, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 169, 170, 1, 0, 0, 0, 170, 172, 1, 0, 0, 0, 171, 169, 1, 0, 0, 0, 172, 173, 5, 0, 0, 1, 173, 1, 1, 0, 0, 0, 174, 177, 3, 6, 3, 0, 175, 177, 3, 12, 6, 0, 176, 174, 1, 0, 0, 0, 176, 175, 1, 0, 0, 0, 177, 3, 1, 0, 0, 0, 178, 179, 3, 106, 53, 0, 179, 5, 1, 0, 0, 0, 180, 181, 5, 50, 0, 0, 181, 185, 3, 150, 75, 0, 182, 183, 5, 111, 0, 0, 183, 184, 5, 118, 0, 0, 184, 186, 3, 4, 2, 0, 185, 182, 1, 0, 0, 0, 185, 186, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 188, 5, 145, 0, 0, 188, 7, 1, 0, 0, 0, 189, 190, 3, 4, 2, 0, 190, 191, 5, 111, 0, 0, 191, 192, 5, 118, 0, 0, 192, 193, 3, 4, 2, 0, 193, 194, 5, 145, 0, 0, 194, 9, 1, 0, 0, 0, 195, 200, 3, 150, 75, 0, 196, 197, 5, 112, 0, 0, 197, 199, 3, 150, 75, 0, 198, 196, 1, 0, 0, 0, 199, 202, 1, 0, 0, 0, 200, 198, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 11, 1, 0, 0, 0, 202, 200, 1, 0, 0, 0, 203, 213, 3, 20, 10, 0, 204, 213, 3, 24, 12, 0, 205, 213, 3, 14, 7, 0, 206, 213, 3, 16, 8, 0, 207, 213, 3, 18, 9, 0, 208, 213, 3, 22, 11, 0, 209, 213, 3, 8, 4, 0, 210, 213, 3, 20, 10, 0, 211, 213, 3, 26, 13, 0, 212, 203, 1, 0, 0, 0, 212, 204, 1, 0, 0, 0, 212, 205, 1, 0, 0, 0, 212, 206, 1, 0, 0, 0, 212, 207, 1, 0, 0, 0, 212, 208, 1, 0, 0, 0, 212, 209, 1, 0, 0, 0, 212, 210, 1, 0, 0, 0, 212, 211, 1, 0, 0, 0, 213, 13, 1, 0, 0, 0, 214, 215, 3, 4, 2, 0, 215, 216, 5, 145, 0, 0, 216, 15, 1, 0, 0, 0, 217, 218, 5, 38, 0, 0, 218, 219, 5, 126, 0, 0, 219, 220, 3, 4, 2, 0, 220, 221, 5, 144, 0, 0, 221, 224, 3, 12, 6, 0, 222, 223, 5, 24, 0, 0, 223, 225, 3, 12, 6, 0, 224, 222, 1, 0, 0, 0, 224, 225, 1, 0, 0, 0, 225, 17, 1, 0, 0, 0, 226, 227, 5, 96, 0, 0, 227, 228, 5, 126, 0, 0, 228, 229, 3, 4, 2, 0, 229, 230, 5, 144, 0, 0, 230, 231, 3, 12, 6, 0, 231, 19, 1, 0, 0, 0, 232, 233, 5, 70, 0, 0, 233, 234, 3, 4, 2, 0, 234, 235, 5, 145, 0, 0, 235, 21, 1, 0, 0, 0, 236, 237, 5, 29, 0, 0, 237, 238, 3, 150, 75, 0, 238, 240, 5, 126, 0, 0, 239, 241, 3, 10, 5, 0, 240, 239, 1, 0, 0, 0, 240, 241, 1, 0, 0, 0, 241, 242, 1, 0, 0, 0, 242, 243, 5, 144, 0, 0, 243, 244, 3, 26, 13, 0, 244, 23, 1, 0, 0, 0, 245, 246, 5, 145, 0, 0, 246, 25, 1, 0, 0, 0, 247, 251, 5, 124, 0, 0, 248, 250, 3, 2, 1, 0, 249, 248, 1, 0, 0, 0, 250, 253, 1, 0, 0, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 254, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 254, 255, 5, 142, 0, 0, 255, 27, 1, 0, 0, 0, 256, 257, 3, 4, 2, 0, 257, 258, 5, 111, 0, 0, 258, 259, 3, 4, 2, 0, 259, 29, 1, 0, 0, 0, 260, 265, 3, 28, 14, 0, 261, 262, 5, 112, 0, 0, 262, 264, 3, 28, 14, 0, 263, 261, 1, 0, 0, 0, 264, 267, 1, 0, 0, 0, 265, 263, 1, 0, 0, 0, 265, 266, 1, 0, 0, 0, 266, 31, 1, 0, 0, 0, 267, 265, 1, 0, 0, 0, 268, 272, 3, 34, 17, 0, 269, 272, 3, 38, 19, 0, 270, 272, 3, 114, 57, 0, 271, 268, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 271, 270, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 274, 5, 0, 0, 1, 274, 33, 1, 0, 0, 0, 275, 281, 3, 36, 18, 0, 276, 277, 5, 91, 0, 0, 277, 278, 5, 1, 0, 0, 278, 280, 3, 36, 18, 0, 279, 276, 1, 0, 0, 0, 280, 283, 1, 0, 0, 0, 281, 279, 1, 0, 0, 0, 281, 282, 1, 0, 0, 0, 282, 35, 1, 0, 0, 0, 283, 281, 1, 0, 0, 0, 284, 291, 3, 38, 19, 0, 285, 286, 5, 126, 0, 0, 286, 287, 3, 34, 17, 0, 287, 288, 5, 144, 0, 0, 288, 291, 1, 0, 0, 0, 289, 291, 3, 154, 77, 0, 290, 284, 1, 0, 0, 0, 290, 285, 1, 0, 0, 0, 290, 289, 1, 0, 0, 0, 291, 37, 1, 0, 0, 0, 292, 294, 3, 40, 20, 0, 293, 292, 1, 0, 0, 0, 293, 294, 1, 0, 0, 0, 294, 295, 1, 0, 0, 0, 295, 297, 5, 77, 0, 0, 296, 298, 5, 23, 0, 0, 297, 296, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 300, 1, 0, 0, 0, 299, 301, 3, 42, 21, 0, 300, 299, 1, 0, 0, 0, 300, 301, 1, 0, 0, 0, 301, 302, 1, 0, 0, 0, 302, 304, 3, 104, 52, 0, 303, 305, 3, 44, 22, 0, 304, 303, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 307, 1, 0, 0, 0, 306, 308, 3, 46, 23, 0, 307, 306, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 310, 1, 0, 0, 0, 309, 311, 3, 50, 25, 0, 310, 309, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 313, 1, 0, 0, 0, 312, 314, 3, 52, 26, 0, 313, 312, 1, 0, 0, 0, 313, 314, 1, 0, 0, 0, 314, 316, 1, 0, 0, 0, 315, 317, 3, 54, 27, 0, 316, 315, 1, 0, 0, 0, 316, 317, 1, 0, 0, 0, 317, 320, 1, 0, 0, 0, 318, 319, 5, 98, 0, 0, 319, 321, 7, 0, 0, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 324, 1, 0, 0, 0, 322, 323, 5, 98, 0, 0, 323, 325, 5, 86, 0, 0, 324, 322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 327, 1, 0, 0, 0, 326, 328, 3, 56, 28, 0, 327, 326, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 330, 1, 0, 0, 0, 329, 331, 3, 48, 24, 0, 330, 329, 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 333, 1, 0, 0, 0, 332, 334, 3, 58, 29, 0, 333, 332, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 337, 1, 0, 0, 0, 335, 338, 3, 62, 31, 0, 336, 338, 3, 64, 32, 0, 337, 335, 1, 0, 0, 0, 337, 336, 1, 0, 0, 0, 337, 338, 1, 0, 0, 0, 338, 340, 1, 0, 0, 0, 339, 341, 3, 66, 33, 0, 340, 339, 1, 0, 0, 0, 340, 341, 1, 0, 0, 0, 341, 39, 1, 0, 0, 0, 342, 343, 5, 98, 0, 0, 343, 344, 3, 118, 59, 0, 344, 41, 1, 0, 0, 0, 345, 346, 5, 85, 0, 0, 346, 349, 5, 104, 0, 0, 347, 348, 5, 98, 0, 0, 348, 350, 5, 82, 0, 0, 349, 347, 1, 0, 0, 0, 349, 350, 1, 0, 0, 0, 350, 43, 1, 0, 0, 0, 351, 352, 5, 32, 0, 0, 352, 353, 3, 68, 34, 0, 353, 45, 1, 0, 0, 0, 354, 356, 7, 1, 0, 0, 355, 354, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 358, 5, 5, 0, 0, 358, 359, 5, 45, 0, 0, 359, 360, 3, 104, 52, 0, 360, 47, 1, 0, 0, 0, 361, 362, 5, 97, 0, 0, 362, 363, 3, 150, 75, 0, 363, 364, 5, 6, 0, 0, 364, 365, 5, 126, 0, 0, 365, 366, 3, 88, 44, 0, 366, 376, 5, 144, 0, 0, 367, 368, 5, 112, 0, 0, 368, 369, 3, 150, 75, 0, 369, 370, 5, 6, 0, 0, 370, 371, 5, 126, 0, 0, 371, 372, 3, 88, 44, 0, 372, 373, 5, 144, 0, 0, 373, 375, 1, 0, 0, 0, 374, 367, 1, 0, 0, 0, 375, 378, 1, 0, 0, 0, 376, 374, 1, 0, 0, 0, 376, 377, 1, 0, 0, 0, 377, 49, 1, 0, 0, 0, 378, 376, 1, 0, 0, 0, 379, 380, 5, 67, 0, 0, 380, 381, 3, 106, 53, 0, 381, 51, 1, 0, 0, 0, 382, 383, 5, 95, 0, 0, 383, 384, 3, 106, 53, 0, 384, 53, 1, 0, 0, 0, 385, 386, 5, 34, 0, 0, 386, 393, 5, 11, 0, 0, 387, 388, 7, 0, 0, 0, 388, 389, 5, 126, 0, 0, 389, 390, 3, 104, 52, 0, 390, 391, 5, 144, 0, 0, 391, 394, 1, 0, 0, 0, 392, 394, 3, 104, 52, 0, 393, 387, 1, 0, 0, 0, 393, 392, 1, 0, 0, 0, 394, 55, 1, 0, 0, 0, 395, 396, 5, 35, 0, 0, 396, 397, 3, 106, 53, 0, 397, 57, 1, 0, 0, 0, 398, 399, 5, 62, 0, 0, 399, 400, 5, 11, 0, 0, 400, 401, 3, 78, 39, 0, 401, 59, 1, 0, 0, 0, 402, 403, 5, 62, 0, 0, 403, 404, 5, 11, 0, 0, 404, 405, 3, 104, 52, 0, 405, 61, 1, 0, 0, 0, 406, 407, 5, 52, 0, 0, 407, 410, 3, 106, 53, 0, 408, 409, 5, 112, 0, 0, 409, 411, 3, 106, 53, 0, 410, 408, 1, 0, 0, 0, 410, 411, 1, 0, 0, 0, 411, 416, 1, 0, 0, 0, 412, 413, 5, 98, 0, 0, 413, 417, 5, 82, 0, 0, 414, 415, 5, 11, 0, 0, 415, 417, 3, 104, 52, 0, 416, 412, 1, 0, 0, 0, 416, 414, 1, 0, 0, 0, 416, 417, 1, 0, 0, 0, 417, 436, 1, 0, 0, 0, 418, 419, 5, 52, 0, 0, 419, 422, 3, 106, 53, 0, 420, 421, 5, 98, 0, 0, 421, 423, 5, 82, 0, 0, 422, 420, 1, 0, 0, 0, 422, 423, 1, 0, 0, 0, 423, 424, 1, 0, 0, 0, 424, 425, 5, 59, 0, 0, 425, 426, 3, 106, 53, 0, 426, 436, 1, 0, 0, 0, 427, 428, 5, 52, 0, 0, 428, 429, 3, 106, 53, 0, 429, 430, 5, 59, 0, 0, 430, 433, 3, 106, 53, 0, 431, 432, 5, 11, 0, 0, 432, 434, 3, 104, 52, 0, 433, 431, 1, 0, 0, 0, 433, 434, 1, 0, 0, 0, 434, 436, 1, 0, 0, 0, 435, 406, 1, 0, 0, 0, 435, 418, 1, 0, 0, 0, 435, 427, 1, 0, 0, 0, 436, 63, 1, 0, 0, 0, 437, 438, 5, 59, 0, 0, 438, 439, 3, 106, 53, 0, 439, 65, 1, 0, 0, 0, 440, 441, 5, 79, 0, 0, 441, 442, 3, 84, 42, 0, 442, 67, 1, 0, 0, 0, 443, 444, 6, 34, -1, 0, 444, 446, 3, 126, 63, 0, 445, 447, 5, 27, 0, 0, 446, 445, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 449, 1, 0, 0, 0, 448, 450, 3, 76, 38, 0, 449, 448, 1, 0, 0, 0, 449, 450, 1, 0, 0, 0, 450, 456, 1, 0, 0, 0, 451, 452, 5, 126, 0, 0, 452, 453, 3, 68, 34, 0, 453, 454, 5, 144, 0, 0, 454, 456, 1, 0, 0, 0, 455, 443, 1, 0, 0, 0, 455, 451, 1, 0, 0, 0, 456, 471, 1, 0, 0, 0, 457, 458, 10, 3, 0, 0, 458, 459, 3, 72, 36, 0, 459, 460, 3, 68, 34, 4, 460, 470, 1, 0, 0, 0, 461, 463, 10, 4, 0, 0, 462, 464, 3, 70, 35, 0, 463, 462, 1, 0, 0, 0, 463, 464, 1, 0, 0, 0, 464, 465, 1, 0, 0, 0, 465, 466, 5, 45, 0, 0, 466, 467, 3, 68, 34, 0, 467, 468, 3, 74, 37, 0, 468, 470, 1, 0, 0, 0, 469, 457, 1, 0, 0, 0, 469, 461, 1, 0, 0, 0, 470, 473, 1, 0, 0, 0, 471, 469, 1, 0, 0, 0, 471, 472, 1, 0, 0, 0, 472, 69, 1, 0, 0, 0, 473, 471, 1, 0, 0, 0, 474, 476, 7, 2, 0, 0, 475, 474, 1, 0, 0, 0, 475, 476, 1, 0, 0, 0, 476, 477, 1, 0, 0, 0, 477, 484, 5, 42, 0, 0, 478, 480, 5, 42, 0, 0, 479, 481, 7, 2, 0, 0, 480, 479, 1, 0, 0, 0, 480, 481, 1, 0, 0, 0, 481, 484, 1, 0, 0, 0, 482, 484, 7, 2, 0, 0, 483, 475, 1, 0, 0, 0, 483, 478, 1, 0, 0, 0, 483, 482, 1, 0, 0, 0, 484, 518, 1, 0, 0, 0, 485, 487, 7, 3, 0, 0, 486, 485, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 488, 1, 0, 0, 0, 488, 490, 7, 4, 0, 0, 489, 491, 5, 63, 0, 0, 490, 489, 1, 0, 0, 0, 490, 491, 1, 0, 0, 0, 491, 500, 1, 0, 0, 0, 492, 494, 7, 4, 0, 0, 493, 495, 5, 63, 0, 0, 494, 493, 1, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, 497, 1, 0, 0, 0, 496, 498, 7, 3, 0, 0, 497, 496, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 500, 1, 0, 0, 0, 499, 486, 1, 0, 0, 0, 499, 492, 1, 0, 0, 0, 500, 518, 1, 0, 0, 0, 501, 503, 7, 5, 0, 0, 502, 501, 1, 0, 0, 0, 502, 503, 1, 0, 0, 0, 503, 504, 1, 0, 0, 0, 504, 506, 5, 33, 0, 0, 505, 507, 5, 63, 0, 0, 506, 505, 1, 0, 0, 0, 506, 507, 1, 0, 0, 0, 507, 516, 1, 0, 0, 0, 508, 510, 5, 33, 0, 0, 509, 511, 5, 63, 0, 0, 510, 509, 1, 0, 0, 0, 510, 511, 1, 0, 0, 0, 511, 513, 1, 0, 0, 0, 512, 514, 7, 5, 0, 0, 513, 512, 1, 0, 0, 0, 513, 514, 1, 0, 0, 0, 514, 516, 1, 0, 0, 0, 515, 502, 1, 0, 0, 0, 515, 508, 1, 0, 0, 0, 516, 518, 1, 0, 0, 0, 517, 483, 1, 0, 0, 0, 517, 499, 1, 0, 0, 0, 517, 515, 1, 0, 0, 0, 518, 71, 1, 0, 0, 0, 519, 520, 5, 16, 0, 0, 520, 523, 5, 45, 0, 0, 521, 523, 5, 112, 0, 0, 522, 519, 1, 0, 0, 0, 522, 521, 1, 0, 0, 0, 523, 73, 1, 0, 0, 0, 524, 525, 5, 60, 0, 0, 525, 534, 3, 104, 52, 0, 526, 527, 5, 92, 0, 0, 527, 528, 5, 126, 0, 0, 528, 529, 3, 104, 52, 0, 529, 530, 5, 144, 0, 0, 530, 534, 1, 0, 0, 0, 531, 532, 5, 92, 0, 0, 532, 534, 3, 104, 52, 0, 533, 524, 1, 0, 0, 0, 533, 526, 1, 0, 0, 0, 533, 531, 1, 0, 0, 0, 534, 75, 1, 0, 0, 0, 535, 536, 5, 75, 0, 0, 536, 539, 3, 82, 41, 0, 537, 538, 5, 59, 0, 0, 538, 540, 3, 82, 41, 0, 539, 537, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 77, 1, 0, 0, 0, 541, 546, 3, 80, 40, 0, 542, 543, 5, 112, 0, 0, 543, 545, 3, 80, 40, 0, 544, 542, 1, 0, 0, 0, 545, 548, 1, 0, 0, 0, 546, 544, 1, 0, 0, 0, 546, 547, 1, 0, 0, 0, 547, 79, 1, 0, 0, 0, 548, 546, 1, 0, 0, 0, 549, 551, 3, 106, 53, 0, 550, 552, 7, 6, 0, 0, 551, 550, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 555, 1, 0, 0, 0, 553, 554, 5, 58, 0, 0, 554, 556, 7, 7, 0, 0, 555, 553, 1, 0, 0, 0, 555, 556, 1, 0, 0, 0, 556, 559, 1, 0, 0, 0, 557, 558, 5, 15, 0, 0, 558, 560, 5, 106, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 81, 1, 0, 0, 0, 561, 568, 3, 154, 77, 0, 562, 565, 3, 138, 69, 0, 563, 564, 5, 146, 0, 0, 564, 566, 3, 138, 69, 0, 565, 563, 1, 0, 0, 0, 565, 566, 1, 0, 0, 0, 566, 568, 1, 0, 0, 0, 567, 561, 1, 0, 0, 0, 567, 562, 1, 0, 0, 0, 568, 83, 1, 0, 0, 0, 569, 574, 3, 86, 43, 0, 570, 571, 5, 112, 0, 0, 571, 573, 3, 86, 43, 0, 572, 570, 1, 0, 0, 0, 573, 576, 1, 0, 0, 0, 574, 572, 1, 0, 0, 0, 574, 575, 1, 0, 0, 0, 575, 85, 1, 0, 0, 0, 576, 574, 1, 0, 0, 0, 577, 578, 3, 150, 75, 0, 578, 579, 5, 118, 0, 0, 579, 580, 3, 140, 70, 0, 580, 87, 1, 0, 0, 0, 581, 583, 3, 90, 45, 0, 582, 581, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 585, 1, 0, 0, 0, 584, 586, 3, 92, 46, 0, 585, 584, 1, 0, 0, 0, 585, 586, 1, 0, 0, 0, 586, 588, 1, 0, 0, 0, 587, 589, 3, 94, 47, 0, 588, 587, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 89, 1, 0, 0, 0, 590, 591, 5, 65, 0, 0, 591, 592, 5, 11, 0, 0, 592, 593, 3, 104, 52, 0, 593, 91, 1, 0, 0, 0, 594, 595, 5, 62, 0, 0, 595, 596, 5, 11, 0, 0, 596, 597, 3, 78, 39, 0, 597, 93, 1, 0, 0, 0, 598, 599, 7, 8, 0, 0, 599, 600, 3, 96, 48, 0, 600, 95, 1, 0, 0, 0, 601, 608, 3, 98, 49, 0, 602, 603, 5, 9, 0, 0, 603, 604, 3, 98, 49, 0, 604, 605, 5, 2, 0, 0, 605, 606, 3, 98, 49, 0, 606, 608, 1, 0, 0, 0, 607, 601, 1, 0, 0, 0, 607, 602, 1, 0, 0, 0, 608, 97, 1, 0, 0, 0, 609, 610, 5, 18, 0, 0, 610, 622, 5, 73, 0, 0, 611, 612, 5, 90, 0, 0, 612, 622, 5, 66, 0, 0, 613, 614, 5, 90, 0, 0, 614, 622, 5, 30, 0, 0, 615, 616, 3, 138, 69, 0, 616, 617, 5, 66, 0, 0, 617, 622, 1, 0, 0, 0, 618, 619, 3, 138, 69, 0, 619, 620, 5, 30, 0, 0, 620, 622, 1, 0, 0, 0, 621, 609, 1, 0, 0, 0, 621, 611, 1, 0, 0, 0, 621, 613, 1, 0, 0, 0, 621, 615, 1, 0, 0, 0, 621, 618, 1, 0, 0, 0, 622, 99, 1, 0, 0, 0, 623, 624, 3, 106, 53, 0, 624, 625, 5, 0, 0, 1, 625, 101, 1, 0, 0, 0, 626, 674, 3, 150, 75, 0, 627, 628, 3, 150, 75, 0, 628, 629, 5, 126, 0, 0, 629, 630, 3, 150, 75, 0, 630, 637, 3, 102, 51, 0, 631, 632, 5, 112, 0, 0, 632, 633, 3, 150, 75, 0, 633, 634, 3, 102, 51, 0, 634, 636, 1, 0, 0, 0, 635, 631, 1, 0, 0, 0, 636, 639, 1, 0, 0, 0, 637, 635, 1, 0, 0, 0, 637, 638, 1, 0, 0, 0, 638, 640, 1, 0, 0, 0, 639, 637, 1, 0, 0, 0, 640, 641, 5, 144, 0, 0, 641, 674, 1, 0, 0, 0, 642, 643, 3, 150, 75, 0, 643, 644, 5, 126, 0, 0, 644, 649, 3, 152, 76, 0, 645, 646, 5, 112, 0, 0, 646, 648, 3, 152, 76, 0, 647, 645, 1, 0, 0, 0, 648, 651, 1, 0, 0, 0, 649, 647, 1, 0, 0, 0, 649, 650, 1, 0, 0, 0, 650, 652, 1, 0, 0, 0, 651, 649, 1, 0, 0, 0, 652, 653, 5, 144, 0, 0, 653, 674, 1, 0, 0, 0, 654, 655, 3, 150, 75, 0, 655, 656, 5, 126, 0, 0, 656, 661, 3, 102, 51, 0, 657, 658, 5, 112, 0, 0, 658, 660, 3, 102, 51, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 664, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 144, 0, 0, 665, 674, 1, 0, 0, 0, 666, 667, 3, 150, 75, 0, 667, 669, 5, 126, 0, 0, 668, 670, 3, 104, 52, 0, 669, 668, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 1, 0, 0, 0, 671, 672, 5, 144, 0, 0, 672, 674, 1, 0, 0, 0, 673, 626, 1, 0, 0, 0, 673, 627, 1, 0, 0, 0, 673, 642, 1, 0, 0, 0, 673, 654, 1, 0, 0, 0, 673, 666, 1, 0, 0, 0, 674, 103, 1, 0, 0, 0, 675, 680, 3, 106, 53, 0, 676, 677, 5, 112, 0, 0, 677, 679, 3, 106, 53, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 105, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 6, 53, -1, 0, 684, 686, 5, 12, 0, 0, 685, 687, 3, 106, 53, 0, 686, 685, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 693, 1, 0, 0, 0, 688, 689, 5, 94, 0, 0, 689, 690, 3, 106, 53, 0, 690, 691, 5, 81, 0, 0, 691, 692, 3, 106, 53, 0, 692, 694, 1, 0, 0, 0, 693, 688, 1, 0, 0, 0, 694, 695, 1, 0, 0, 0, 695, 693, 1, 0, 0, 0, 695, 696, 1, 0, 0, 0, 696, 699, 1, 0, 0, 0, 697, 698, 5, 24, 0, 0, 698, 700, 3, 106, 53, 0, 699, 697, 1, 0, 0, 0, 699, 700, 1, 0, 0, 0, 700, 701, 1, 0, 0, 0, 701, 702, 5, 25, 0, 0, 702, 833, 1, 0, 0, 0, 703, 704, 5, 13, 0, 0, 704, 705, 5, 126, 0, 0, 705, 706, 3, 106, 53, 0, 706, 707, 5, 6, 0, 0, 707, 708, 3, 102, 51, 0, 708, 709, 5, 144, 0, 0, 709, 833, 1, 0, 0, 0, 710, 711, 5, 19, 0, 0, 711, 833, 5, 106, 0, 0, 712, 713, 5, 43, 0, 0, 713, 714, 3, 106, 53, 0, 714, 715, 3, 142, 71, 0, 715, 833, 1, 0, 0, 0, 716, 717, 5, 80, 0, 0, 717, 718, 5, 126, 0, 0, 718, 719, 3, 106, 53, 0, 719, 720, 5, 32, 0, 0, 720, 723, 3, 106, 53, 0, 721, 722, 5, 31, 0, 0, 722, 724, 3, 106, 53, 0, 723, 721, 1, 0, 0, 0, 723, 724, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 726, 5, 144, 0, 0, 726, 833, 1, 0, 0, 0, 727, 728, 5, 83, 0, 0, 728, 833, 5, 106, 0, 0, 729, 730, 5, 88, 0, 0, 730, 731, 5, 126, 0, 0, 731, 732, 7, 9, 0, 0, 732, 733, 3, 156, 78, 0, 733, 734, 5, 32, 0, 0, 734, 735, 3, 106, 53, 0, 735, 736, 5, 144, 0, 0, 736, 833, 1, 0, 0, 0, 737, 738, 3, 150, 75, 0, 738, 740, 5, 126, 0, 0, 739, 741, 3, 104, 52, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 144, 0, 0, 743, 752, 1, 0, 0, 0, 744, 746, 5, 126, 0, 0, 745, 747, 5, 23, 0, 0, 746, 745, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 749, 1, 0, 0, 0, 748, 750, 3, 108, 54, 0, 749, 748, 1, 0, 0, 0, 749, 750, 1, 0, 0, 0, 750, 751, 1, 0, 0, 0, 751, 753, 5, 144, 0, 0, 752, 744, 1, 0, 0, 0, 752, 753, 1, 0, 0, 0, 753, 754, 1, 0, 0, 0, 754, 755, 5, 64, 0, 0, 755, 756, 5, 126, 0, 0, 756, 757, 3, 88, 44, 0, 757, 758, 5, 144, 0, 0, 758, 833, 1, 0, 0, 0, 759, 760, 3, 150, 75, 0, 760, 762, 5, 126, 0, 0, 761, 763, 3, 104, 52, 0, 762, 761, 1, 0, 0, 0, 762, 763, 1, 0, 0, 0, 763, 764, 1, 0, 0, 0, 764, 765, 5, 144, 0, 0, 765, 774, 1, 0, 0, 0, 766, 768, 5, 126, 0, 0, 767, 769, 5, 23, 0, 0, 768, 767, 1, 0, 0, 0, 768, 769, 1, 0, 0, 0, 769, 771, 1, 0, 0, 0, 770, 772, 3, 108, 54, 0, 771, 770, 1, 0, 0, 0, 771, 772, 1, 0, 0, 0, 772, 773, 1, 0, 0, 0, 773, 775, 5, 144, 0, 0, 774, 766, 1, 0, 0, 0, 774, 775, 1, 0, 0, 0, 775, 776, 1, 0, 0, 0, 776, 777, 5, 64, 0, 0, 777, 778, 3, 150, 75, 0, 778, 833, 1, 0, 0, 0, 779, 785, 3, 150, 75, 0, 780, 782, 5, 126, 0, 0, 781, 783, 3, 104, 52, 0, 782, 781, 1, 0, 0, 0, 782, 783, 1, 0, 0, 0, 783, 784, 1, 0, 0, 0, 784, 786, 5, 144, 0, 0, 785, 780, 1, 0, 0, 0, 785, 786, 1, 0, 0, 0, 786, 787, 1, 0, 0, 0, 787, 789, 5, 126, 0, 0, 788, 790, 5, 23, 0, 0, 789, 788, 1, 0, 0, 0, 789, 790, 1, 0, 0, 0, 790, 792, 1, 0, 0, 0, 791, 793, 3, 108, 54, 0, 792, 791, 1, 0, 0, 0, 792, 793, 1, 0, 0, 0, 793, 794, 1, 0, 0, 0, 794, 795, 5, 144, 0, 0, 795, 833, 1, 0, 0, 0, 796, 833, 3, 114, 57, 0, 797, 833, 3, 158, 79, 0, 798, 833, 3, 140, 70, 0, 799, 800, 5, 114, 0, 0, 800, 833, 3, 106, 53, 19, 801, 802, 5, 56, 0, 0, 802, 833, 3, 106, 53, 13, 803, 804, 3, 130, 65, 0, 804, 805, 5, 116, 0, 0, 805, 807, 1, 0, 0, 0, 806, 803, 1, 0, 0, 0, 806, 807, 1, 0, 0, 0, 807, 808, 1, 0, 0, 0, 808, 833, 5, 108, 0, 0, 809, 810, 5, 126, 0, 0, 810, 811, 3, 34, 17, 0, 811, 812, 5, 144, 0, 0, 812, 833, 1, 0, 0, 0, 813, 814, 5, 126, 0, 0, 814, 815, 3, 106, 53, 0, 815, 816, 5, 144, 0, 0, 816, 833, 1, 0, 0, 0, 817, 818, 5, 126, 0, 0, 818, 819, 3, 104, 52, 0, 819, 820, 5, 144, 0, 0, 820, 833, 1, 0, 0, 0, 821, 823, 5, 125, 0, 0, 822, 824, 3, 104, 52, 0, 823, 822, 1, 0, 0, 0, 823, 824, 1, 0, 0, 0, 824, 825, 1, 0, 0, 0, 825, 833, 5, 143, 0, 0, 826, 828, 5, 124, 0, 0, 827, 829, 3, 30, 15, 0, 828, 827, 1, 0, 0, 0, 828, 829, 1, 0, 0, 0, 829, 830, 1, 0, 0, 0, 830, 833, 5, 142, 0, 0, 831, 833, 3, 122, 61, 0, 832, 683, 1, 0, 0, 0, 832, 703, 1, 0, 0, 0, 832, 710, 1, 0, 0, 0, 832, 712, 1, 0, 0, 0, 832, 716, 1, 0, 0, 0, 832, 727, 1, 0, 0, 0, 832, 729, 1, 0, 0, 0, 832, 737, 1, 0, 0, 0, 832, 759, 1, 0, 0, 0, 832, 779, 1, 0, 0, 0, 832, 796, 1, 0, 0, 0, 832, 797, 1, 0, 0, 0, 832, 798, 1, 0, 0, 0, 832, 799, 1, 0, 0, 0, 832, 801, 1, 0, 0, 0, 832, 806, 1, 0, 0, 0, 832, 809, 1, 0, 0, 0, 832, 813, 1, 0, 0, 0, 832, 817, 1, 0, 0, 0, 832, 821, 1, 0, 0, 0, 832, 826, 1, 0, 0, 0, 832, 831, 1, 0, 0, 0, 833, 927, 1, 0, 0, 0, 834, 838, 10, 18, 0, 0, 835, 839, 5, 108, 0, 0, 836, 839, 5, 146, 0, 0, 837, 839, 5, 133, 0, 0, 838, 835, 1, 0, 0, 0, 838, 836, 1, 0, 0, 0, 838, 837, 1, 0, 0, 0, 839, 840, 1, 0, 0, 0, 840, 926, 3, 106, 53, 19, 841, 845, 10, 17, 0, 0, 842, 846, 5, 134, 0, 0, 843, 846, 5, 114, 0, 0, 844, 846, 5, 113, 0, 0, 845, 842, 1, 0, 0, 0, 845, 843, 1, 0, 0, 0, 845, 844, 1, 0, 0, 0, 846, 847, 1, 0, 0, 0, 847, 926, 3, 106, 53, 18, 848, 873, 10, 16, 0, 0, 849, 874, 5, 117, 0, 0, 850, 874, 5, 118, 0, 0, 851, 874, 5, 129, 0, 0, 852, 874, 5, 127, 0, 0, 853, 874, 5, 128, 0, 0, 854, 874, 5, 119, 0, 0, 855, 874, 5, 120, 0, 0, 856, 858, 5, 56, 0, 0, 857, 856, 1, 0, 0, 0, 857, 858, 1, 0, 0, 0, 858, 859, 1, 0, 0, 0, 859, 861, 5, 40, 0, 0, 860, 862, 5, 14, 0, 0, 861, 860, 1, 0, 0, 0, 861, 862, 1, 0, 0, 0, 862, 874, 1, 0, 0, 0, 863, 865, 5, 56, 0, 0, 864, 863, 1, 0, 0, 0, 864, 865, 1, 0, 0, 0, 865, 866, 1, 0, 0, 0, 866, 874, 7, 10, 0, 0, 867, 874, 5, 140, 0, 0, 868, 874, 5, 141, 0, 0, 869, 874, 5, 131, 0, 0, 870, 874, 5, 122, 0, 0, 871, 874, 5, 123, 0, 0, 872, 874, 5, 130, 0, 0, 873, 849, 1, 0, 0, 0, 873, 850, 1, 0, 0, 0, 873, 851, 1, 0, 0, 0, 873, 852, 1, 0, 0, 0, 873, 853, 1, 0, 0, 0, 873, 854, 1, 0, 0, 0, 873, 855, 1, 0, 0, 0, 873, 857, 1, 0, 0, 0, 873, 864, 1, 0, 0, 0, 873, 867, 1, 0, 0, 0, 873, 868, 1, 0, 0, 0, 873, 869, 1, 0, 0, 0, 873, 870, 1, 0, 0, 0, 873, 871, 1, 0, 0, 0, 873, 872, 1, 0, 0, 0, 874, 875, 1, 0, 0, 0, 875, 926, 3, 106, 53, 17, 876, 877, 10, 14, 0, 0, 877, 878, 5, 132, 0, 0, 878, 926, 3, 106, 53, 15, 879, 880, 10, 12, 0, 0, 880, 881, 5, 2, 0, 0, 881, 926, 3, 106, 53, 13, 882, 883, 10, 11, 0, 0, 883, 884, 5, 61, 0, 0, 884, 926, 3, 106, 53, 12, 885, 887, 10, 10, 0, 0, 886, 888, 5, 56, 0, 0, 887, 886, 1, 0, 0, 0, 887, 888, 1, 0, 0, 0, 888, 889, 1, 0, 0, 0, 889, 890, 5, 9, 0, 0, 890, 891, 3, 106, 53, 0, 891, 892, 5, 2, 0, 0, 892, 893, 3, 106, 53, 11, 893, 926, 1, 0, 0, 0, 894, 895, 10, 9, 0, 0, 895, 896, 5, 135, 0, 0, 896, 897, 3, 106, 53, 0, 897, 898, 5, 111, 0, 0, 898, 899, 3, 106, 53, 9, 899, 926, 1, 0, 0, 0, 900, 901, 10, 22, 0, 0, 901, 902, 5, 125, 0, 0, 902, 903, 3, 106, 53, 0, 903, 904, 5, 143, 0, 0, 904, 926, 1, 0, 0, 0, 905, 906, 10, 21, 0, 0, 906, 907, 5, 116, 0, 0, 907, 926, 5, 104, 0, 0, 908, 909, 10, 20, 0, 0, 909, 910, 5, 116, 0, 0, 910, 926, 3, 150, 75, 0, 911, 912, 10, 15, 0, 0, 912, 914, 5, 44, 0, 0, 913, 915, 5, 56, 0, 0, 914, 913, 1, 0, 0, 0, 914, 915, 1, 0, 0, 0, 915, 916, 1, 0, 0, 0, 916, 926, 5, 57, 0, 0, 917, 923, 10, 8, 0, 0, 918, 924, 3, 148, 74, 0, 919, 920, 5, 6, 0, 0, 920, 924, 3, 150, 75, 0, 921, 922, 5, 6, 0, 0, 922, 924, 5, 106, 0, 0, 923, 918, 1, 0, 0, 0, 923, 919, 1, 0, 0, 0, 923, 921, 1, 0, 0, 0, 924, 926, 1, 0, 0, 0, 925, 834, 1, 0, 0, 0, 925, 841, 1, 0, 0, 0, 925, 848, 1, 0, 0, 0, 925, 876, 1, 0, 0, 0, 925, 879, 1, 0, 0, 0, 925, 882, 1, 0, 0, 0, 925, 885, 1, 0, 0, 0, 925, 894, 1, 0, 0, 0, 925, 900, 1, 0, 0, 0, 925, 905, 1, 0, 0, 0, 925, 908, 1, 0, 0, 0, 925, 911, 1, 0, 0, 0, 925, 917, 1, 0, 0, 0, 926, 929, 1, 0, 0, 0, 927, 925, 1, 0, 0, 0, 927, 928, 1, 0, 0, 0, 928, 107, 1, 0, 0, 0, 929, 927, 1, 0, 0, 0, 930, 935, 3, 110, 55, 0, 931, 932, 5, 112, 0, 0, 932, 934, 3, 110, 55, 0, 933, 931, 1, 0, 0, 0, 934, 937, 1, 0, 0, 0, 935, 933, 1, 0, 0, 0, 935, 936, 1, 0, 0, 0, 936, 109, 1, 0, 0, 0, 937, 935, 1, 0, 0, 0, 938, 941, 3, 112, 56, 0, 939, 941, 3, 106, 53, 0, 940, 938, 1, 0, 0, 0, 940, 939, 1, 0, 0, 0, 941, 111, 1, 0, 0, 0, 942, 943, 5, 126, 0, 0, 943, 948, 3, 150, 75, 0, 944, 945, 5, 112, 0, 0, 945, 947, 3, 150, 75, 0, 946, 944, 1, 0, 0, 0, 947, 950, 1, 0, 0, 0, 948, 946, 1, 0, 0, 0, 948, 949, 1, 0, 0, 0, 949, 951, 1, 0, 0, 0, 950, 948, 1, 0, 0, 0, 951, 952, 5, 144, 0, 0, 952, 962, 1, 0, 0, 0, 953, 958, 3, 150, 75, 0, 954, 955, 5, 112, 0, 0, 955, 957, 3, 150, 75, 0, 956, 954, 1, 0, 0, 0, 957, 960, 1, 0, 0, 0, 958, 956, 1, 0, 0, 0, 958, 959, 1, 0, 0, 0, 959, 962, 1, 0, 0, 0, 960, 958, 1, 0, 0, 0, 961, 942, 1, 0, 0, 0, 961, 953, 1, 0, 0, 0, 962, 963, 1, 0, 0, 0, 963, 964, 5, 107, 0, 0, 964, 965, 3, 106, 53, 0, 965, 113, 1, 0, 0, 0, 966, 967, 5, 128, 0, 0, 967, 971, 3, 150, 75, 0, 968, 970, 3, 116, 58, 0, 969, 968, 1, 0, 0, 0, 970, 973, 1, 0, 0, 0, 971, 969, 1, 0, 0, 0, 971, 972, 1, 0, 0, 0, 972, 974, 1, 0, 0, 0, 973, 971, 1, 0, 0, 0, 974, 975, 5, 146, 0, 0, 975, 976, 5, 120, 0, 0, 976, 995, 1, 0, 0, 0, 977, 978, 5, 128, 0, 0, 978, 982, 3, 150, 75, 0, 979, 981, 3, 116, 58, 0, 980, 979, 1, 0, 0, 0, 981, 984, 1, 0, 0, 0, 982, 980, 1, 0, 0, 0, 982, 983, 1, 0, 0, 0, 983, 985, 1, 0, 0, 0, 984, 982, 1, 0, 0, 0, 985, 987, 5, 120, 0, 0, 986, 988, 3, 114, 57, 0, 987, 986, 1, 0, 0, 0, 987, 988, 1, 0, 0, 0, 988, 989, 1, 0, 0, 0, 989, 990, 5, 128, 0, 0, 990, 991, 5, 146, 0, 0, 991, 992, 3, 150, 75, 0, 992, 993, 5, 120, 0, 0, 993, 995, 1, 0, 0, 0, 994, 966, 1, 0, 0, 0, 994, 977, 1, 0, 0, 0, 995, 115, 1, 0, 0, 0, 996, 997, 3, 150, 75, 0, 997, 998, 5, 118, 0, 0, 998, 999, 3, 156, 78, 0, 999, 1008, 1, 0, 0, 0, 1000, 1001, 3, 150, 75, 0, 1001, 1002, 5, 118, 0, 0, 1002, 1003, 5, 124, 0, 0, 1003, 1004, 3, 106, 53, 0, 1004, 1005, 5, 142, 0, 0, 1005, 1008, 1, 0, 0, 0, 1006, 1008, 3, 150, 75, 0, 1007, 996, 1, 0, 0, 0, 1007, 1000, 1, 0, 0, 0, 1007, 1006, 1, 0, 0, 0, 1008, 117, 1, 0, 0, 0, 1009, 1014, 3, 120, 60, 0, 1010, 1011, 5, 112, 0, 0, 1011, 1013, 3, 120, 60, 0, 1012, 1010, 1, 0, 0, 0, 1013, 1016, 1, 0, 0, 0, 1014, 1012, 1, 0, 0, 0, 1014, 1015, 1, 0, 0, 0, 1015, 119, 1, 0, 0, 0, 1016, 1014, 1, 0, 0, 0, 1017, 1018, 3, 150, 75, 0, 1018, 1019, 5, 6, 0, 0, 1019, 1020, 5, 126, 0, 0, 1020, 1021, 3, 34, 17, 0, 1021, 1022, 5, 144, 0, 0, 1022, 1028, 1, 0, 0, 0, 1023, 1024, 3, 106, 53, 0, 1024, 1025, 5, 6, 0, 0, 1025, 1026, 3, 150, 75, 0, 1026, 1028, 1, 0, 0, 0, 1027, 1017, 1, 0, 0, 0, 1027, 1023, 1, 0, 0, 0, 1028, 121, 1, 0, 0, 0, 1029, 1037, 3, 154, 77, 0, 1030, 1031, 3, 130, 65, 0, 1031, 1032, 5, 116, 0, 0, 1032, 1034, 1, 0, 0, 0, 1033, 1030, 1, 0, 0, 0, 1033, 1034, 1, 0, 0, 0, 1034, 1035, 1, 0, 0, 0, 1035, 1037, 3, 124, 62, 0, 1036, 1029, 1, 0, 0, 0, 1036, 1033, 1, 0, 0, 0, 1037, 123, 1, 0, 0, 0, 1038, 1043, 3, 150, 75, 0, 1039, 1040, 5, 116, 0, 0, 1040, 1042, 3, 150, 75, 0, 1041, 1039, 1, 0, 0, 0, 1042, 1045, 1, 0, 0, 0, 1043, 1041, 1, 0, 0, 0, 1043, 1044, 1, 0, 0, 0, 1044, 125, 1, 0, 0, 0, 1045, 1043, 1, 0, 0, 0, 1046, 1047, 6, 63, -1, 0, 1047, 1056, 3, 130, 65, 0, 1048, 1056, 3, 128, 64, 0, 1049, 1050, 5, 126, 0, 0, 1050, 1051, 3, 34, 17, 0, 1051, 1052, 5, 144, 0, 0, 1052, 1056, 1, 0, 0, 0, 1053, 1056, 3, 114, 57, 0, 1054, 1056, 3, 154, 77, 0, 1055, 1046, 1, 0, 0, 0, 1055, 1048, 1, 0, 0, 0, 1055, 1049, 1, 0, 0, 0, 1055, 1053, 1, 0, 0, 0, 1055, 1054, 1, 0, 0, 0, 1056, 1065, 1, 0, 0, 0, 1057, 1061, 10, 3, 0, 0, 1058, 1062, 3, 148, 74, 0, 1059, 1060, 5, 6, 0, 0, 1060, 1062, 3, 150, 75, 0, 1061, 1058, 1, 0, 0, 0, 1061, 1059, 1, 0, 0, 0, 1062, 1064, 1, 0, 0, 0, 1063, 1057, 1, 0, 0, 0, 1064, 1067, 1, 0, 0, 0, 1065, 1063, 1, 0, 0, 0, 1065, 1066, 1, 0, 0, 0, 1066, 127, 1, 0, 0, 0, 1067, 1065, 1, 0, 0, 0, 1068, 1069, 3, 150, 75, 0, 1069, 1071, 5, 126, 0, 0, 1070, 1072, 3, 132, 66, 0, 1071, 1070, 1, 0, 0, 0, 1071, 1072, 1, 0, 0, 0, 1072, 1073, 1, 0, 0, 0, 1073, 1074, 5, 144, 0, 0, 1074, 129, 1, 0, 0, 0, 1075, 1076, 3, 134, 67, 0, 1076, 1077, 5, 116, 0, 0, 1077, 1079, 1, 0, 0, 0, 1078, 1075, 1, 0, 0, 0, 1078, 1079, 1, 0, 0, 0, 1079, 1080, 1, 0, 0, 0, 1080, 1081, 3, 150, 75, 0, 1081, 131, 1, 0, 0, 0, 1082, 1087, 3, 106, 53, 0, 1083, 1084, 5, 112, 0, 0, 1084, 1086, 3, 106, 53, 0, 1085, 1083, 1, 0, 0, 0, 1086, 1089, 1, 0, 0, 0, 1087, 1085, 1, 0, 0, 0, 1087, 1088, 1, 0, 0, 0, 1088, 133, 1, 0, 0, 0, 1089, 1087, 1, 0, 0, 0, 1090, 1091, 3, 150, 75, 0, 1091, 135, 1, 0, 0, 0, 1092, 1101, 5, 102, 0, 0, 1093, 1094, 5, 116, 0, 0, 1094, 1101, 7, 11, 0, 0, 1095, 1096, 5, 104, 0, 0, 1096, 1098, 5, 116, 0, 0, 1097, 1099, 7, 11, 0, 0, 1098, 1097, 1, 0, 0, 0, 1098, 1099, 1, 0, 0, 0, 1099, 1101, 1, 0, 0, 0, 1100, 1092, 1, 0, 0, 0, 1100, 1093, 1, 0, 0, 0, 1100, 1095, 1, 0, 0, 0, 1101, 137, 1, 0, 0, 0, 1102, 1104, 7, 12, 0, 0, 1103, 1102, 1, 0, 0, 0, 1103, 1104, 1, 0, 0, 0, 1104, 1111, 1, 0, 0, 0, 1105, 1112, 3, 136, 68, 0, 1106, 1112, 5, 103, 0, 0, 1107, 1112, 5, 104, 0, 0, 1108, 1112, 5, 105, 0, 0, 1109, 1112, 5, 41, 0, 0, 1110, 1112, 5, 55, 0, 0, 1111, 1105, 1, 0, 0, 0, 1111, 1106, 1, 0, 0, 0, 1111, 1107, 1, 0, 0, 0, 1111, 1108, 1, 0, 0, 0, 1111, 1109, 1, 0, 0, 0, 1111, 1110, 1, 0, 0, 0, 1112, 139, 1, 0, 0, 0, 1113, 1117, 3, 138, 69, 0, 1114, 1117, 5, 106, 0, 0, 1115, 1117, 5, 57, 0, 0, 1116, 1113, 1, 0, 0, 0, 1116, 1114, 1, 0, 0, 0, 1116, 1115, 1, 0, 0, 0, 1117, 141, 1, 0, 0, 0, 1118, 1119, 7, 13, 0, 0, 1119, 143, 1, 0, 0, 0, 1120, 1121, 7, 14, 0, 0, 1121, 145, 1, 0, 0, 0, 1122, 1123, 7, 15, 0, 0, 1123, 147, 1, 0, 0, 0, 1124, 1127, 5, 101, 0, 0, 1125, 1127, 3, 146, 73, 0, 1126, 1124, 1, 0, 0, 0, 1126, 1125, 1, 0, 0, 0, 1127, 149, 1, 0, 0, 0, 1128, 1132, 5, 101, 0, 0, 1129, 1132, 3, 142, 71, 0, 1130, 1132, 3, 144, 72, 0, 1131, 1128, 1, 0, 0, 0, 1131, 1129, 1, 0, 0, 0, 1131, 1130, 1, 0, 0, 0, 1132, 151, 1, 0, 0, 0, 1133, 1134, 3, 156, 78, 0, 1134, 1135, 5, 118, 0, 0, 1135, 1136, 3, 138, 69, 0, 1136, 153, 1, 0, 0, 0, 1137, 1138, 5, 124, 0, 0, 1138, 1139, 3, 150, 75, 0, 1139, 1140, 5, 142, 0, 0, 1140, 155, 1, 0, 0, 0, 1141, 1144, 5, 106, 0, 0, 1142, 1144, 3, 158, 79, 0, 1143, 1141, 1, 0, 0, 0, 1143, 1142, 1, 0, 0, 0, 1144, 157, 1, 0, 0, 0, 1145, 1149, 5, 137, 0, 0, 1146, 1148, 3, 160, 80, 0, 1147, 1146, 1, 0, 0, 0, 1148, 1151, 1, 0, 0, 0, 1149, 1147, 1, 0, 0, 0, 1149, 1150, 1, 0, 0, 0, 1150, 1152, 1, 0, 0, 0, 1151, 1149, 1, 0, 0, 0, 1152, 1153, 5, 139, 0, 0, 1153, 159, 1, 0, 0, 0, 1154, 1155, 5, 152, 0, 0, 1155, 1156, 3, 106, 53, 0, 1156, 1157, 5, 142, 0, 0, 1157, 1160, 1, 0, 0, 0, 1158, 1160, 5, 151, 0, 0, 1159, 1154, 1, 0, 0, 0, 1159, 1158, 1, 0, 0, 0, 1160, 161, 1, 0, 0, 0, 1161, 1165, 5, 138, 0, 0, 1162, 1164, 3, 164, 82, 0, 1163, 1162, 1, 0, 0, 0, 1164, 1167, 1, 0, 0, 0, 1165, 1163, 1, 0, 0, 0, 1165, 1166, 1, 0, 0, 0, 1166, 1168, 1, 0, 0, 0, 1167, 1165, 1, 0, 0, 0, 1168, 1169, 5, 0, 0, 1, 1169, 163, 1, 0, 0, 0, 1170, 1171, 5, 154, 0, 0, 1171, 1172, 3, 106, 53, 0, 1172, 1173, 5, 142, 0, 0, 1173, 1176, 1, 0, 0, 0, 1174, 1176, 5, 153, 0, 0, 1175, 1170, 1, 0, 0, 0, 1175, 1174, 1, 0, 0, 0, 1176, 165, 1, 0, 0, 0, 141, 169, 176, 185, 200, 212, 224, 240, 251, 265, 271, 281, 290, 293, 297, 300, 304, 307, 310, 313, 316, 320, 324, 327, 330, 333, 337, 340, 349, 355, 376, 393, 410, 416, 422, 433, 435, 446, 449, 455, 463, 469, 471, 475, 480, 483, 486, 490, 494, 497, 499, 502, 506, 510, 513, 515, 517, 522, 533, 539, 546, 551, 555, 559, 565, 567, 574, 582, 585, 588, 607, 621, 637, 649, 661, 669, 673, 680, 686, 695, 699, 723, 740, 746, 749, 752, 762, 768, 771, 774, 782, 785, 789, 792, 806, 823, 828, 832, 838, 845, 857, 861, 864, 873, 887, 914, 923, 925, 927, 935, 940, 948, 958, 961, 971, 982, 987, 994, 1007, 1014, 1027, 1033, 1036, 1043, 1055, 1061, 1065, 1071, 1078, 1087, 1098, 1100, 1103, 1111, 1116, 1126, 1131, 1143, 1149, 1159, 1165, 1175] \ No newline at end of file diff --git a/hogql_parser/parser.cpp b/hogql_parser/parser.cpp index 06e5ffb2e9d37f..274aa741ae24ad 100644 --- a/hogql_parser/parser.cpp +++ b/hogql_parser/parser.cpp @@ -1622,27 +1622,42 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor { auto column_expr_list_ctx = ctx->columnExprList(); string name = visitAsString(ctx->identifier(0)); string over_identifier = visitAsString(ctx->identifier(1)); - PyObject* args = visitAsPyObjectOrEmptyList(column_expr_list_ctx); + PyObject* exprs = visitAsPyObjectOrEmptyList(column_expr_list_ctx); + PyObject* args; + try { + args = visitAsPyObjectOrEmptyList(ctx->columnArgList()); + } catch (...) { + Py_DECREF(exprs); + throw; + } RETURN_NEW_AST_NODE( - "WindowFunction", "{s:s#,s:N,s:s#}", "name", name.data(), name.size(), "args", args, "over_identifier", - over_identifier.data(), over_identifier.size() + "WindowFunction", "{s:s#,s:N,s:N,s:s#}", "name", name.data(), name.size(), "exprs", exprs, "args", args, + "over_identifier", over_identifier.data(), over_identifier.size() ); } VISIT(ColumnExprWinFunction) { string identifier = visitAsString(ctx->identifier()); auto column_expr_list_ctx = ctx->columnExprList(); - PyObject* args = visitAsPyObjectOrEmptyList(column_expr_list_ctx); + PyObject* exprs = visitAsPyObjectOrEmptyList(column_expr_list_ctx); + PyObject* args; + try { + args = visitAsPyObjectOrEmptyList(ctx->columnArgList()); + } catch (...) { + Py_DECREF(exprs); + throw; + } PyObject* over_expr; try { over_expr = visitAsPyObjectOrNone(ctx->windowExpr()); } catch (...) { + Py_DECREF(exprs); Py_DECREF(args); throw; } RETURN_NEW_AST_NODE( - "WindowFunction", "{s:s#,s:N,s:N}", "name", identifier.data(), identifier.size(), "args", args, "over_expr", - over_expr + "WindowFunction", "{s:s#,s:N,s:N,s:N}", "name", identifier.data(), identifier.size(), "exprs", exprs, + "args", args, "over_expr", over_expr ); } diff --git a/hogql_parser/setup.py b/hogql_parser/setup.py index 030b98ddb58be7..ae4aff4cf8581a 100644 --- a/hogql_parser/setup.py +++ b/hogql_parser/setup.py @@ -32,7 +32,7 @@ setup( name="hogql_parser", - version="1.0.11", + version="1.0.12", url="https://github.com/PostHog/posthog/tree/master/hogql_parser", author="PostHog Inc.", author_email="hey@posthog.com", diff --git a/posthog/hogql/ast.py b/posthog/hogql/ast.py index d296a354b7c50c..d9e71e34e4cacb 100644 --- a/posthog/hogql/ast.py +++ b/posthog/hogql/ast.py @@ -717,6 +717,7 @@ class WindowExpr(Expr): class WindowFunction(Expr): name: str args: Optional[list[Expr]] = None + exprs: Optional[list[Expr]] = None over_expr: Optional[WindowExpr] = None over_identifier: Optional[str] = None diff --git a/posthog/hogql/grammar/HogQLParser.g4 b/posthog/hogql/grammar/HogQLParser.g4 index bc3c954de10aab..911f5827073d01 100644 --- a/posthog/hogql/grammar/HogQLParser.g4 +++ b/posthog/hogql/grammar/HogQLParser.g4 @@ -140,9 +140,9 @@ columnExpr | SUBSTRING LPAREN columnExpr FROM columnExpr (FOR columnExpr)? RPAREN # ColumnExprSubstring | TIMESTAMP STRING_LITERAL # ColumnExprTimestamp | TRIM LPAREN (BOTH | LEADING | TRAILING) string FROM columnExpr RPAREN # ColumnExprTrim - | identifier (LPAREN columnExprList? RPAREN) OVER LPAREN windowExpr RPAREN # ColumnExprWinFunction - | identifier (LPAREN columnExprList? RPAREN) OVER identifier # ColumnExprWinFunctionTarget - | identifier (LPAREN columnExprList? RPAREN)? LPAREN DISTINCT? columnArgList? RPAREN # ColumnExprFunction + | identifier (LPAREN columnExprList? RPAREN) (LPAREN DISTINCT? columnArgList? RPAREN)? OVER LPAREN windowExpr RPAREN # ColumnExprWinFunction + | identifier (LPAREN columnExprList? RPAREN) (LPAREN DISTINCT? columnArgList? RPAREN)? OVER identifier # ColumnExprWinFunctionTarget + | identifier (LPAREN columnExprList? RPAREN)? LPAREN DISTINCT? columnArgList? RPAREN # ColumnExprFunction | hogqlxTagElement # ColumnExprTagElement | templateString # ColumnExprTemplateString | literal # ColumnExprLiteral diff --git a/posthog/hogql/grammar/HogQLParser.interp b/posthog/hogql/grammar/HogQLParser.interp index a2f030a7eb8fa6..086eca220c32f7 100644 --- a/posthog/hogql/grammar/HogQLParser.interp +++ b/posthog/hogql/grammar/HogQLParser.interp @@ -399,4 +399,4 @@ stringContentsFull atn: -[4, 1, 154, 1158, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 1, 0, 5, 0, 168, 8, 0, 10, 0, 12, 0, 171, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 177, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 186, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 5, 5, 199, 8, 5, 10, 5, 12, 5, 202, 9, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 213, 8, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 225, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 241, 8, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 5, 13, 250, 8, 13, 10, 13, 12, 13, 253, 9, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 5, 15, 264, 8, 15, 10, 15, 12, 15, 267, 9, 15, 1, 16, 1, 16, 1, 16, 3, 16, 272, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 5, 17, 280, 8, 17, 10, 17, 12, 17, 283, 9, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 3, 18, 291, 8, 18, 1, 19, 3, 19, 294, 8, 19, 1, 19, 1, 19, 3, 19, 298, 8, 19, 1, 19, 3, 19, 301, 8, 19, 1, 19, 1, 19, 3, 19, 305, 8, 19, 1, 19, 3, 19, 308, 8, 19, 1, 19, 3, 19, 311, 8, 19, 1, 19, 3, 19, 314, 8, 19, 1, 19, 3, 19, 317, 8, 19, 1, 19, 1, 19, 3, 19, 321, 8, 19, 1, 19, 1, 19, 3, 19, 325, 8, 19, 1, 19, 3, 19, 328, 8, 19, 1, 19, 3, 19, 331, 8, 19, 1, 19, 3, 19, 334, 8, 19, 1, 19, 1, 19, 3, 19, 338, 8, 19, 1, 19, 3, 19, 341, 8, 19, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 350, 8, 21, 1, 22, 1, 22, 1, 22, 1, 23, 3, 23, 356, 8, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 375, 8, 24, 10, 24, 12, 24, 378, 9, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 3, 27, 394, 8, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 411, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 417, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 423, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 434, 8, 31, 3, 31, 436, 8, 31, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 3, 34, 447, 8, 34, 1, 34, 3, 34, 450, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 456, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 464, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 470, 8, 34, 10, 34, 12, 34, 473, 9, 34, 1, 35, 3, 35, 476, 8, 35, 1, 35, 1, 35, 1, 35, 3, 35, 481, 8, 35, 1, 35, 3, 35, 484, 8, 35, 1, 35, 3, 35, 487, 8, 35, 1, 35, 1, 35, 3, 35, 491, 8, 35, 1, 35, 1, 35, 3, 35, 495, 8, 35, 1, 35, 3, 35, 498, 8, 35, 3, 35, 500, 8, 35, 1, 35, 3, 35, 503, 8, 35, 1, 35, 1, 35, 3, 35, 507, 8, 35, 1, 35, 1, 35, 3, 35, 511, 8, 35, 1, 35, 3, 35, 514, 8, 35, 3, 35, 516, 8, 35, 3, 35, 518, 8, 35, 1, 36, 1, 36, 1, 36, 3, 36, 523, 8, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 3, 37, 534, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 540, 8, 38, 1, 39, 1, 39, 1, 39, 5, 39, 545, 8, 39, 10, 39, 12, 39, 548, 9, 39, 1, 40, 1, 40, 3, 40, 552, 8, 40, 1, 40, 1, 40, 3, 40, 556, 8, 40, 1, 40, 1, 40, 3, 40, 560, 8, 40, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 566, 8, 41, 3, 41, 568, 8, 41, 1, 42, 1, 42, 1, 42, 5, 42, 573, 8, 42, 10, 42, 12, 42, 576, 9, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 3, 44, 583, 8, 44, 1, 44, 3, 44, 586, 8, 44, 1, 44, 3, 44, 589, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 3, 48, 608, 8, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 622, 8, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 636, 8, 51, 10, 51, 12, 51, 639, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 648, 8, 51, 10, 51, 12, 51, 651, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 660, 8, 51, 10, 51, 12, 51, 663, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, 670, 8, 51, 1, 51, 1, 51, 3, 51, 674, 8, 51, 1, 52, 1, 52, 1, 52, 5, 52, 679, 8, 52, 10, 52, 12, 52, 682, 9, 52, 1, 53, 1, 53, 1, 53, 3, 53, 687, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 4, 53, 694, 8, 53, 11, 53, 12, 53, 695, 1, 53, 1, 53, 3, 53, 700, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 724, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 741, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 753, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 763, 8, 53, 1, 53, 3, 53, 766, 8, 53, 1, 53, 1, 53, 3, 53, 770, 8, 53, 1, 53, 3, 53, 773, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 787, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 804, 8, 53, 1, 53, 1, 53, 1, 53, 3, 53, 809, 8, 53, 1, 53, 1, 53, 3, 53, 813, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 819, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 826, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 838, 8, 53, 1, 53, 1, 53, 3, 53, 842, 8, 53, 1, 53, 3, 53, 845, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 854, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 868, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 895, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 904, 8, 53, 5, 53, 906, 8, 53, 10, 53, 12, 53, 909, 9, 53, 1, 54, 1, 54, 1, 54, 5, 54, 914, 8, 54, 10, 54, 12, 54, 917, 9, 54, 1, 55, 1, 55, 3, 55, 921, 8, 55, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 927, 8, 56, 10, 56, 12, 56, 930, 9, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 937, 8, 56, 10, 56, 12, 56, 940, 9, 56, 3, 56, 942, 8, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 5, 57, 950, 8, 57, 10, 57, 12, 57, 953, 9, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 5, 57, 961, 8, 57, 10, 57, 12, 57, 964, 9, 57, 1, 57, 1, 57, 3, 57, 968, 8, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 3, 57, 975, 8, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 988, 8, 58, 1, 59, 1, 59, 1, 59, 5, 59, 993, 8, 59, 10, 59, 12, 59, 996, 9, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 1008, 8, 60, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 1014, 8, 61, 1, 61, 3, 61, 1017, 8, 61, 1, 62, 1, 62, 1, 62, 5, 62, 1022, 8, 62, 10, 62, 12, 62, 1025, 9, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1036, 8, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1042, 8, 63, 5, 63, 1044, 8, 63, 10, 63, 12, 63, 1047, 9, 63, 1, 64, 1, 64, 1, 64, 3, 64, 1052, 8, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 3, 65, 1059, 8, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 5, 66, 1066, 8, 66, 10, 66, 12, 66, 1069, 9, 66, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 3, 68, 1079, 8, 68, 3, 68, 1081, 8, 68, 1, 69, 3, 69, 1084, 8, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 3, 69, 1092, 8, 69, 1, 70, 1, 70, 1, 70, 3, 70, 1097, 8, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 3, 74, 1107, 8, 74, 1, 75, 1, 75, 1, 75, 3, 75, 1112, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 3, 78, 1124, 8, 78, 1, 79, 1, 79, 5, 79, 1128, 8, 79, 10, 79, 12, 79, 1131, 9, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 3, 80, 1140, 8, 80, 1, 81, 1, 81, 5, 81, 1144, 8, 81, 10, 81, 12, 81, 1147, 9, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 3, 82, 1156, 8, 82, 1, 82, 0, 3, 68, 106, 126, 83, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 0, 16, 2, 0, 17, 17, 72, 72, 2, 0, 42, 42, 49, 49, 3, 0, 1, 1, 4, 4, 8, 8, 4, 0, 1, 1, 3, 4, 8, 8, 78, 78, 2, 0, 49, 49, 71, 71, 2, 0, 1, 1, 4, 4, 2, 0, 7, 7, 21, 22, 2, 0, 28, 28, 47, 47, 2, 0, 69, 69, 74, 74, 3, 0, 10, 10, 48, 48, 87, 87, 2, 0, 39, 39, 51, 51, 1, 0, 103, 104, 2, 0, 114, 114, 134, 134, 7, 0, 20, 20, 36, 36, 53, 54, 68, 68, 76, 76, 93, 93, 99, 99, 12, 0, 1, 19, 21, 28, 30, 35, 37, 40, 42, 49, 51, 52, 56, 56, 58, 67, 69, 75, 77, 92, 94, 95, 97, 98, 4, 0, 19, 19, 28, 28, 37, 37, 46, 46, 1288, 0, 169, 1, 0, 0, 0, 2, 176, 1, 0, 0, 0, 4, 178, 1, 0, 0, 0, 6, 180, 1, 0, 0, 0, 8, 189, 1, 0, 0, 0, 10, 195, 1, 0, 0, 0, 12, 212, 1, 0, 0, 0, 14, 214, 1, 0, 0, 0, 16, 217, 1, 0, 0, 0, 18, 226, 1, 0, 0, 0, 20, 232, 1, 0, 0, 0, 22, 236, 1, 0, 0, 0, 24, 245, 1, 0, 0, 0, 26, 247, 1, 0, 0, 0, 28, 256, 1, 0, 0, 0, 30, 260, 1, 0, 0, 0, 32, 271, 1, 0, 0, 0, 34, 275, 1, 0, 0, 0, 36, 290, 1, 0, 0, 0, 38, 293, 1, 0, 0, 0, 40, 342, 1, 0, 0, 0, 42, 345, 1, 0, 0, 0, 44, 351, 1, 0, 0, 0, 46, 355, 1, 0, 0, 0, 48, 361, 1, 0, 0, 0, 50, 379, 1, 0, 0, 0, 52, 382, 1, 0, 0, 0, 54, 385, 1, 0, 0, 0, 56, 395, 1, 0, 0, 0, 58, 398, 1, 0, 0, 0, 60, 402, 1, 0, 0, 0, 62, 435, 1, 0, 0, 0, 64, 437, 1, 0, 0, 0, 66, 440, 1, 0, 0, 0, 68, 455, 1, 0, 0, 0, 70, 517, 1, 0, 0, 0, 72, 522, 1, 0, 0, 0, 74, 533, 1, 0, 0, 0, 76, 535, 1, 0, 0, 0, 78, 541, 1, 0, 0, 0, 80, 549, 1, 0, 0, 0, 82, 567, 1, 0, 0, 0, 84, 569, 1, 0, 0, 0, 86, 577, 1, 0, 0, 0, 88, 582, 1, 0, 0, 0, 90, 590, 1, 0, 0, 0, 92, 594, 1, 0, 0, 0, 94, 598, 1, 0, 0, 0, 96, 607, 1, 0, 0, 0, 98, 621, 1, 0, 0, 0, 100, 623, 1, 0, 0, 0, 102, 673, 1, 0, 0, 0, 104, 675, 1, 0, 0, 0, 106, 812, 1, 0, 0, 0, 108, 910, 1, 0, 0, 0, 110, 920, 1, 0, 0, 0, 112, 941, 1, 0, 0, 0, 114, 974, 1, 0, 0, 0, 116, 987, 1, 0, 0, 0, 118, 989, 1, 0, 0, 0, 120, 1007, 1, 0, 0, 0, 122, 1016, 1, 0, 0, 0, 124, 1018, 1, 0, 0, 0, 126, 1035, 1, 0, 0, 0, 128, 1048, 1, 0, 0, 0, 130, 1058, 1, 0, 0, 0, 132, 1062, 1, 0, 0, 0, 134, 1070, 1, 0, 0, 0, 136, 1080, 1, 0, 0, 0, 138, 1083, 1, 0, 0, 0, 140, 1096, 1, 0, 0, 0, 142, 1098, 1, 0, 0, 0, 144, 1100, 1, 0, 0, 0, 146, 1102, 1, 0, 0, 0, 148, 1106, 1, 0, 0, 0, 150, 1111, 1, 0, 0, 0, 152, 1113, 1, 0, 0, 0, 154, 1117, 1, 0, 0, 0, 156, 1123, 1, 0, 0, 0, 158, 1125, 1, 0, 0, 0, 160, 1139, 1, 0, 0, 0, 162, 1141, 1, 0, 0, 0, 164, 1155, 1, 0, 0, 0, 166, 168, 3, 2, 1, 0, 167, 166, 1, 0, 0, 0, 168, 171, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 169, 170, 1, 0, 0, 0, 170, 172, 1, 0, 0, 0, 171, 169, 1, 0, 0, 0, 172, 173, 5, 0, 0, 1, 173, 1, 1, 0, 0, 0, 174, 177, 3, 6, 3, 0, 175, 177, 3, 12, 6, 0, 176, 174, 1, 0, 0, 0, 176, 175, 1, 0, 0, 0, 177, 3, 1, 0, 0, 0, 178, 179, 3, 106, 53, 0, 179, 5, 1, 0, 0, 0, 180, 181, 5, 50, 0, 0, 181, 185, 3, 150, 75, 0, 182, 183, 5, 111, 0, 0, 183, 184, 5, 118, 0, 0, 184, 186, 3, 4, 2, 0, 185, 182, 1, 0, 0, 0, 185, 186, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 188, 5, 145, 0, 0, 188, 7, 1, 0, 0, 0, 189, 190, 3, 4, 2, 0, 190, 191, 5, 111, 0, 0, 191, 192, 5, 118, 0, 0, 192, 193, 3, 4, 2, 0, 193, 194, 5, 145, 0, 0, 194, 9, 1, 0, 0, 0, 195, 200, 3, 150, 75, 0, 196, 197, 5, 112, 0, 0, 197, 199, 3, 150, 75, 0, 198, 196, 1, 0, 0, 0, 199, 202, 1, 0, 0, 0, 200, 198, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 11, 1, 0, 0, 0, 202, 200, 1, 0, 0, 0, 203, 213, 3, 20, 10, 0, 204, 213, 3, 24, 12, 0, 205, 213, 3, 14, 7, 0, 206, 213, 3, 16, 8, 0, 207, 213, 3, 18, 9, 0, 208, 213, 3, 22, 11, 0, 209, 213, 3, 8, 4, 0, 210, 213, 3, 20, 10, 0, 211, 213, 3, 26, 13, 0, 212, 203, 1, 0, 0, 0, 212, 204, 1, 0, 0, 0, 212, 205, 1, 0, 0, 0, 212, 206, 1, 0, 0, 0, 212, 207, 1, 0, 0, 0, 212, 208, 1, 0, 0, 0, 212, 209, 1, 0, 0, 0, 212, 210, 1, 0, 0, 0, 212, 211, 1, 0, 0, 0, 213, 13, 1, 0, 0, 0, 214, 215, 3, 4, 2, 0, 215, 216, 5, 145, 0, 0, 216, 15, 1, 0, 0, 0, 217, 218, 5, 38, 0, 0, 218, 219, 5, 126, 0, 0, 219, 220, 3, 4, 2, 0, 220, 221, 5, 144, 0, 0, 221, 224, 3, 12, 6, 0, 222, 223, 5, 24, 0, 0, 223, 225, 3, 12, 6, 0, 224, 222, 1, 0, 0, 0, 224, 225, 1, 0, 0, 0, 225, 17, 1, 0, 0, 0, 226, 227, 5, 96, 0, 0, 227, 228, 5, 126, 0, 0, 228, 229, 3, 4, 2, 0, 229, 230, 5, 144, 0, 0, 230, 231, 3, 12, 6, 0, 231, 19, 1, 0, 0, 0, 232, 233, 5, 70, 0, 0, 233, 234, 3, 4, 2, 0, 234, 235, 5, 145, 0, 0, 235, 21, 1, 0, 0, 0, 236, 237, 5, 29, 0, 0, 237, 238, 3, 150, 75, 0, 238, 240, 5, 126, 0, 0, 239, 241, 3, 10, 5, 0, 240, 239, 1, 0, 0, 0, 240, 241, 1, 0, 0, 0, 241, 242, 1, 0, 0, 0, 242, 243, 5, 144, 0, 0, 243, 244, 3, 26, 13, 0, 244, 23, 1, 0, 0, 0, 245, 246, 5, 145, 0, 0, 246, 25, 1, 0, 0, 0, 247, 251, 5, 124, 0, 0, 248, 250, 3, 2, 1, 0, 249, 248, 1, 0, 0, 0, 250, 253, 1, 0, 0, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 254, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 254, 255, 5, 142, 0, 0, 255, 27, 1, 0, 0, 0, 256, 257, 3, 4, 2, 0, 257, 258, 5, 111, 0, 0, 258, 259, 3, 4, 2, 0, 259, 29, 1, 0, 0, 0, 260, 265, 3, 28, 14, 0, 261, 262, 5, 112, 0, 0, 262, 264, 3, 28, 14, 0, 263, 261, 1, 0, 0, 0, 264, 267, 1, 0, 0, 0, 265, 263, 1, 0, 0, 0, 265, 266, 1, 0, 0, 0, 266, 31, 1, 0, 0, 0, 267, 265, 1, 0, 0, 0, 268, 272, 3, 34, 17, 0, 269, 272, 3, 38, 19, 0, 270, 272, 3, 114, 57, 0, 271, 268, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 271, 270, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 274, 5, 0, 0, 1, 274, 33, 1, 0, 0, 0, 275, 281, 3, 36, 18, 0, 276, 277, 5, 91, 0, 0, 277, 278, 5, 1, 0, 0, 278, 280, 3, 36, 18, 0, 279, 276, 1, 0, 0, 0, 280, 283, 1, 0, 0, 0, 281, 279, 1, 0, 0, 0, 281, 282, 1, 0, 0, 0, 282, 35, 1, 0, 0, 0, 283, 281, 1, 0, 0, 0, 284, 291, 3, 38, 19, 0, 285, 286, 5, 126, 0, 0, 286, 287, 3, 34, 17, 0, 287, 288, 5, 144, 0, 0, 288, 291, 1, 0, 0, 0, 289, 291, 3, 154, 77, 0, 290, 284, 1, 0, 0, 0, 290, 285, 1, 0, 0, 0, 290, 289, 1, 0, 0, 0, 291, 37, 1, 0, 0, 0, 292, 294, 3, 40, 20, 0, 293, 292, 1, 0, 0, 0, 293, 294, 1, 0, 0, 0, 294, 295, 1, 0, 0, 0, 295, 297, 5, 77, 0, 0, 296, 298, 5, 23, 0, 0, 297, 296, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 300, 1, 0, 0, 0, 299, 301, 3, 42, 21, 0, 300, 299, 1, 0, 0, 0, 300, 301, 1, 0, 0, 0, 301, 302, 1, 0, 0, 0, 302, 304, 3, 104, 52, 0, 303, 305, 3, 44, 22, 0, 304, 303, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 307, 1, 0, 0, 0, 306, 308, 3, 46, 23, 0, 307, 306, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 310, 1, 0, 0, 0, 309, 311, 3, 50, 25, 0, 310, 309, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 313, 1, 0, 0, 0, 312, 314, 3, 52, 26, 0, 313, 312, 1, 0, 0, 0, 313, 314, 1, 0, 0, 0, 314, 316, 1, 0, 0, 0, 315, 317, 3, 54, 27, 0, 316, 315, 1, 0, 0, 0, 316, 317, 1, 0, 0, 0, 317, 320, 1, 0, 0, 0, 318, 319, 5, 98, 0, 0, 319, 321, 7, 0, 0, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 324, 1, 0, 0, 0, 322, 323, 5, 98, 0, 0, 323, 325, 5, 86, 0, 0, 324, 322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 327, 1, 0, 0, 0, 326, 328, 3, 56, 28, 0, 327, 326, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 330, 1, 0, 0, 0, 329, 331, 3, 48, 24, 0, 330, 329, 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 333, 1, 0, 0, 0, 332, 334, 3, 58, 29, 0, 333, 332, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 337, 1, 0, 0, 0, 335, 338, 3, 62, 31, 0, 336, 338, 3, 64, 32, 0, 337, 335, 1, 0, 0, 0, 337, 336, 1, 0, 0, 0, 337, 338, 1, 0, 0, 0, 338, 340, 1, 0, 0, 0, 339, 341, 3, 66, 33, 0, 340, 339, 1, 0, 0, 0, 340, 341, 1, 0, 0, 0, 341, 39, 1, 0, 0, 0, 342, 343, 5, 98, 0, 0, 343, 344, 3, 118, 59, 0, 344, 41, 1, 0, 0, 0, 345, 346, 5, 85, 0, 0, 346, 349, 5, 104, 0, 0, 347, 348, 5, 98, 0, 0, 348, 350, 5, 82, 0, 0, 349, 347, 1, 0, 0, 0, 349, 350, 1, 0, 0, 0, 350, 43, 1, 0, 0, 0, 351, 352, 5, 32, 0, 0, 352, 353, 3, 68, 34, 0, 353, 45, 1, 0, 0, 0, 354, 356, 7, 1, 0, 0, 355, 354, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 358, 5, 5, 0, 0, 358, 359, 5, 45, 0, 0, 359, 360, 3, 104, 52, 0, 360, 47, 1, 0, 0, 0, 361, 362, 5, 97, 0, 0, 362, 363, 3, 150, 75, 0, 363, 364, 5, 6, 0, 0, 364, 365, 5, 126, 0, 0, 365, 366, 3, 88, 44, 0, 366, 376, 5, 144, 0, 0, 367, 368, 5, 112, 0, 0, 368, 369, 3, 150, 75, 0, 369, 370, 5, 6, 0, 0, 370, 371, 5, 126, 0, 0, 371, 372, 3, 88, 44, 0, 372, 373, 5, 144, 0, 0, 373, 375, 1, 0, 0, 0, 374, 367, 1, 0, 0, 0, 375, 378, 1, 0, 0, 0, 376, 374, 1, 0, 0, 0, 376, 377, 1, 0, 0, 0, 377, 49, 1, 0, 0, 0, 378, 376, 1, 0, 0, 0, 379, 380, 5, 67, 0, 0, 380, 381, 3, 106, 53, 0, 381, 51, 1, 0, 0, 0, 382, 383, 5, 95, 0, 0, 383, 384, 3, 106, 53, 0, 384, 53, 1, 0, 0, 0, 385, 386, 5, 34, 0, 0, 386, 393, 5, 11, 0, 0, 387, 388, 7, 0, 0, 0, 388, 389, 5, 126, 0, 0, 389, 390, 3, 104, 52, 0, 390, 391, 5, 144, 0, 0, 391, 394, 1, 0, 0, 0, 392, 394, 3, 104, 52, 0, 393, 387, 1, 0, 0, 0, 393, 392, 1, 0, 0, 0, 394, 55, 1, 0, 0, 0, 395, 396, 5, 35, 0, 0, 396, 397, 3, 106, 53, 0, 397, 57, 1, 0, 0, 0, 398, 399, 5, 62, 0, 0, 399, 400, 5, 11, 0, 0, 400, 401, 3, 78, 39, 0, 401, 59, 1, 0, 0, 0, 402, 403, 5, 62, 0, 0, 403, 404, 5, 11, 0, 0, 404, 405, 3, 104, 52, 0, 405, 61, 1, 0, 0, 0, 406, 407, 5, 52, 0, 0, 407, 410, 3, 106, 53, 0, 408, 409, 5, 112, 0, 0, 409, 411, 3, 106, 53, 0, 410, 408, 1, 0, 0, 0, 410, 411, 1, 0, 0, 0, 411, 416, 1, 0, 0, 0, 412, 413, 5, 98, 0, 0, 413, 417, 5, 82, 0, 0, 414, 415, 5, 11, 0, 0, 415, 417, 3, 104, 52, 0, 416, 412, 1, 0, 0, 0, 416, 414, 1, 0, 0, 0, 416, 417, 1, 0, 0, 0, 417, 436, 1, 0, 0, 0, 418, 419, 5, 52, 0, 0, 419, 422, 3, 106, 53, 0, 420, 421, 5, 98, 0, 0, 421, 423, 5, 82, 0, 0, 422, 420, 1, 0, 0, 0, 422, 423, 1, 0, 0, 0, 423, 424, 1, 0, 0, 0, 424, 425, 5, 59, 0, 0, 425, 426, 3, 106, 53, 0, 426, 436, 1, 0, 0, 0, 427, 428, 5, 52, 0, 0, 428, 429, 3, 106, 53, 0, 429, 430, 5, 59, 0, 0, 430, 433, 3, 106, 53, 0, 431, 432, 5, 11, 0, 0, 432, 434, 3, 104, 52, 0, 433, 431, 1, 0, 0, 0, 433, 434, 1, 0, 0, 0, 434, 436, 1, 0, 0, 0, 435, 406, 1, 0, 0, 0, 435, 418, 1, 0, 0, 0, 435, 427, 1, 0, 0, 0, 436, 63, 1, 0, 0, 0, 437, 438, 5, 59, 0, 0, 438, 439, 3, 106, 53, 0, 439, 65, 1, 0, 0, 0, 440, 441, 5, 79, 0, 0, 441, 442, 3, 84, 42, 0, 442, 67, 1, 0, 0, 0, 443, 444, 6, 34, -1, 0, 444, 446, 3, 126, 63, 0, 445, 447, 5, 27, 0, 0, 446, 445, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 449, 1, 0, 0, 0, 448, 450, 3, 76, 38, 0, 449, 448, 1, 0, 0, 0, 449, 450, 1, 0, 0, 0, 450, 456, 1, 0, 0, 0, 451, 452, 5, 126, 0, 0, 452, 453, 3, 68, 34, 0, 453, 454, 5, 144, 0, 0, 454, 456, 1, 0, 0, 0, 455, 443, 1, 0, 0, 0, 455, 451, 1, 0, 0, 0, 456, 471, 1, 0, 0, 0, 457, 458, 10, 3, 0, 0, 458, 459, 3, 72, 36, 0, 459, 460, 3, 68, 34, 4, 460, 470, 1, 0, 0, 0, 461, 463, 10, 4, 0, 0, 462, 464, 3, 70, 35, 0, 463, 462, 1, 0, 0, 0, 463, 464, 1, 0, 0, 0, 464, 465, 1, 0, 0, 0, 465, 466, 5, 45, 0, 0, 466, 467, 3, 68, 34, 0, 467, 468, 3, 74, 37, 0, 468, 470, 1, 0, 0, 0, 469, 457, 1, 0, 0, 0, 469, 461, 1, 0, 0, 0, 470, 473, 1, 0, 0, 0, 471, 469, 1, 0, 0, 0, 471, 472, 1, 0, 0, 0, 472, 69, 1, 0, 0, 0, 473, 471, 1, 0, 0, 0, 474, 476, 7, 2, 0, 0, 475, 474, 1, 0, 0, 0, 475, 476, 1, 0, 0, 0, 476, 477, 1, 0, 0, 0, 477, 484, 5, 42, 0, 0, 478, 480, 5, 42, 0, 0, 479, 481, 7, 2, 0, 0, 480, 479, 1, 0, 0, 0, 480, 481, 1, 0, 0, 0, 481, 484, 1, 0, 0, 0, 482, 484, 7, 2, 0, 0, 483, 475, 1, 0, 0, 0, 483, 478, 1, 0, 0, 0, 483, 482, 1, 0, 0, 0, 484, 518, 1, 0, 0, 0, 485, 487, 7, 3, 0, 0, 486, 485, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 488, 1, 0, 0, 0, 488, 490, 7, 4, 0, 0, 489, 491, 5, 63, 0, 0, 490, 489, 1, 0, 0, 0, 490, 491, 1, 0, 0, 0, 491, 500, 1, 0, 0, 0, 492, 494, 7, 4, 0, 0, 493, 495, 5, 63, 0, 0, 494, 493, 1, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, 497, 1, 0, 0, 0, 496, 498, 7, 3, 0, 0, 497, 496, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 500, 1, 0, 0, 0, 499, 486, 1, 0, 0, 0, 499, 492, 1, 0, 0, 0, 500, 518, 1, 0, 0, 0, 501, 503, 7, 5, 0, 0, 502, 501, 1, 0, 0, 0, 502, 503, 1, 0, 0, 0, 503, 504, 1, 0, 0, 0, 504, 506, 5, 33, 0, 0, 505, 507, 5, 63, 0, 0, 506, 505, 1, 0, 0, 0, 506, 507, 1, 0, 0, 0, 507, 516, 1, 0, 0, 0, 508, 510, 5, 33, 0, 0, 509, 511, 5, 63, 0, 0, 510, 509, 1, 0, 0, 0, 510, 511, 1, 0, 0, 0, 511, 513, 1, 0, 0, 0, 512, 514, 7, 5, 0, 0, 513, 512, 1, 0, 0, 0, 513, 514, 1, 0, 0, 0, 514, 516, 1, 0, 0, 0, 515, 502, 1, 0, 0, 0, 515, 508, 1, 0, 0, 0, 516, 518, 1, 0, 0, 0, 517, 483, 1, 0, 0, 0, 517, 499, 1, 0, 0, 0, 517, 515, 1, 0, 0, 0, 518, 71, 1, 0, 0, 0, 519, 520, 5, 16, 0, 0, 520, 523, 5, 45, 0, 0, 521, 523, 5, 112, 0, 0, 522, 519, 1, 0, 0, 0, 522, 521, 1, 0, 0, 0, 523, 73, 1, 0, 0, 0, 524, 525, 5, 60, 0, 0, 525, 534, 3, 104, 52, 0, 526, 527, 5, 92, 0, 0, 527, 528, 5, 126, 0, 0, 528, 529, 3, 104, 52, 0, 529, 530, 5, 144, 0, 0, 530, 534, 1, 0, 0, 0, 531, 532, 5, 92, 0, 0, 532, 534, 3, 104, 52, 0, 533, 524, 1, 0, 0, 0, 533, 526, 1, 0, 0, 0, 533, 531, 1, 0, 0, 0, 534, 75, 1, 0, 0, 0, 535, 536, 5, 75, 0, 0, 536, 539, 3, 82, 41, 0, 537, 538, 5, 59, 0, 0, 538, 540, 3, 82, 41, 0, 539, 537, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 77, 1, 0, 0, 0, 541, 546, 3, 80, 40, 0, 542, 543, 5, 112, 0, 0, 543, 545, 3, 80, 40, 0, 544, 542, 1, 0, 0, 0, 545, 548, 1, 0, 0, 0, 546, 544, 1, 0, 0, 0, 546, 547, 1, 0, 0, 0, 547, 79, 1, 0, 0, 0, 548, 546, 1, 0, 0, 0, 549, 551, 3, 106, 53, 0, 550, 552, 7, 6, 0, 0, 551, 550, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 555, 1, 0, 0, 0, 553, 554, 5, 58, 0, 0, 554, 556, 7, 7, 0, 0, 555, 553, 1, 0, 0, 0, 555, 556, 1, 0, 0, 0, 556, 559, 1, 0, 0, 0, 557, 558, 5, 15, 0, 0, 558, 560, 5, 106, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 81, 1, 0, 0, 0, 561, 568, 3, 154, 77, 0, 562, 565, 3, 138, 69, 0, 563, 564, 5, 146, 0, 0, 564, 566, 3, 138, 69, 0, 565, 563, 1, 0, 0, 0, 565, 566, 1, 0, 0, 0, 566, 568, 1, 0, 0, 0, 567, 561, 1, 0, 0, 0, 567, 562, 1, 0, 0, 0, 568, 83, 1, 0, 0, 0, 569, 574, 3, 86, 43, 0, 570, 571, 5, 112, 0, 0, 571, 573, 3, 86, 43, 0, 572, 570, 1, 0, 0, 0, 573, 576, 1, 0, 0, 0, 574, 572, 1, 0, 0, 0, 574, 575, 1, 0, 0, 0, 575, 85, 1, 0, 0, 0, 576, 574, 1, 0, 0, 0, 577, 578, 3, 150, 75, 0, 578, 579, 5, 118, 0, 0, 579, 580, 3, 140, 70, 0, 580, 87, 1, 0, 0, 0, 581, 583, 3, 90, 45, 0, 582, 581, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 585, 1, 0, 0, 0, 584, 586, 3, 92, 46, 0, 585, 584, 1, 0, 0, 0, 585, 586, 1, 0, 0, 0, 586, 588, 1, 0, 0, 0, 587, 589, 3, 94, 47, 0, 588, 587, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 89, 1, 0, 0, 0, 590, 591, 5, 65, 0, 0, 591, 592, 5, 11, 0, 0, 592, 593, 3, 104, 52, 0, 593, 91, 1, 0, 0, 0, 594, 595, 5, 62, 0, 0, 595, 596, 5, 11, 0, 0, 596, 597, 3, 78, 39, 0, 597, 93, 1, 0, 0, 0, 598, 599, 7, 8, 0, 0, 599, 600, 3, 96, 48, 0, 600, 95, 1, 0, 0, 0, 601, 608, 3, 98, 49, 0, 602, 603, 5, 9, 0, 0, 603, 604, 3, 98, 49, 0, 604, 605, 5, 2, 0, 0, 605, 606, 3, 98, 49, 0, 606, 608, 1, 0, 0, 0, 607, 601, 1, 0, 0, 0, 607, 602, 1, 0, 0, 0, 608, 97, 1, 0, 0, 0, 609, 610, 5, 18, 0, 0, 610, 622, 5, 73, 0, 0, 611, 612, 5, 90, 0, 0, 612, 622, 5, 66, 0, 0, 613, 614, 5, 90, 0, 0, 614, 622, 5, 30, 0, 0, 615, 616, 3, 138, 69, 0, 616, 617, 5, 66, 0, 0, 617, 622, 1, 0, 0, 0, 618, 619, 3, 138, 69, 0, 619, 620, 5, 30, 0, 0, 620, 622, 1, 0, 0, 0, 621, 609, 1, 0, 0, 0, 621, 611, 1, 0, 0, 0, 621, 613, 1, 0, 0, 0, 621, 615, 1, 0, 0, 0, 621, 618, 1, 0, 0, 0, 622, 99, 1, 0, 0, 0, 623, 624, 3, 106, 53, 0, 624, 625, 5, 0, 0, 1, 625, 101, 1, 0, 0, 0, 626, 674, 3, 150, 75, 0, 627, 628, 3, 150, 75, 0, 628, 629, 5, 126, 0, 0, 629, 630, 3, 150, 75, 0, 630, 637, 3, 102, 51, 0, 631, 632, 5, 112, 0, 0, 632, 633, 3, 150, 75, 0, 633, 634, 3, 102, 51, 0, 634, 636, 1, 0, 0, 0, 635, 631, 1, 0, 0, 0, 636, 639, 1, 0, 0, 0, 637, 635, 1, 0, 0, 0, 637, 638, 1, 0, 0, 0, 638, 640, 1, 0, 0, 0, 639, 637, 1, 0, 0, 0, 640, 641, 5, 144, 0, 0, 641, 674, 1, 0, 0, 0, 642, 643, 3, 150, 75, 0, 643, 644, 5, 126, 0, 0, 644, 649, 3, 152, 76, 0, 645, 646, 5, 112, 0, 0, 646, 648, 3, 152, 76, 0, 647, 645, 1, 0, 0, 0, 648, 651, 1, 0, 0, 0, 649, 647, 1, 0, 0, 0, 649, 650, 1, 0, 0, 0, 650, 652, 1, 0, 0, 0, 651, 649, 1, 0, 0, 0, 652, 653, 5, 144, 0, 0, 653, 674, 1, 0, 0, 0, 654, 655, 3, 150, 75, 0, 655, 656, 5, 126, 0, 0, 656, 661, 3, 102, 51, 0, 657, 658, 5, 112, 0, 0, 658, 660, 3, 102, 51, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 664, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 144, 0, 0, 665, 674, 1, 0, 0, 0, 666, 667, 3, 150, 75, 0, 667, 669, 5, 126, 0, 0, 668, 670, 3, 104, 52, 0, 669, 668, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 1, 0, 0, 0, 671, 672, 5, 144, 0, 0, 672, 674, 1, 0, 0, 0, 673, 626, 1, 0, 0, 0, 673, 627, 1, 0, 0, 0, 673, 642, 1, 0, 0, 0, 673, 654, 1, 0, 0, 0, 673, 666, 1, 0, 0, 0, 674, 103, 1, 0, 0, 0, 675, 680, 3, 106, 53, 0, 676, 677, 5, 112, 0, 0, 677, 679, 3, 106, 53, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 105, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 6, 53, -1, 0, 684, 686, 5, 12, 0, 0, 685, 687, 3, 106, 53, 0, 686, 685, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 693, 1, 0, 0, 0, 688, 689, 5, 94, 0, 0, 689, 690, 3, 106, 53, 0, 690, 691, 5, 81, 0, 0, 691, 692, 3, 106, 53, 0, 692, 694, 1, 0, 0, 0, 693, 688, 1, 0, 0, 0, 694, 695, 1, 0, 0, 0, 695, 693, 1, 0, 0, 0, 695, 696, 1, 0, 0, 0, 696, 699, 1, 0, 0, 0, 697, 698, 5, 24, 0, 0, 698, 700, 3, 106, 53, 0, 699, 697, 1, 0, 0, 0, 699, 700, 1, 0, 0, 0, 700, 701, 1, 0, 0, 0, 701, 702, 5, 25, 0, 0, 702, 813, 1, 0, 0, 0, 703, 704, 5, 13, 0, 0, 704, 705, 5, 126, 0, 0, 705, 706, 3, 106, 53, 0, 706, 707, 5, 6, 0, 0, 707, 708, 3, 102, 51, 0, 708, 709, 5, 144, 0, 0, 709, 813, 1, 0, 0, 0, 710, 711, 5, 19, 0, 0, 711, 813, 5, 106, 0, 0, 712, 713, 5, 43, 0, 0, 713, 714, 3, 106, 53, 0, 714, 715, 3, 142, 71, 0, 715, 813, 1, 0, 0, 0, 716, 717, 5, 80, 0, 0, 717, 718, 5, 126, 0, 0, 718, 719, 3, 106, 53, 0, 719, 720, 5, 32, 0, 0, 720, 723, 3, 106, 53, 0, 721, 722, 5, 31, 0, 0, 722, 724, 3, 106, 53, 0, 723, 721, 1, 0, 0, 0, 723, 724, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 726, 5, 144, 0, 0, 726, 813, 1, 0, 0, 0, 727, 728, 5, 83, 0, 0, 728, 813, 5, 106, 0, 0, 729, 730, 5, 88, 0, 0, 730, 731, 5, 126, 0, 0, 731, 732, 7, 9, 0, 0, 732, 733, 3, 156, 78, 0, 733, 734, 5, 32, 0, 0, 734, 735, 3, 106, 53, 0, 735, 736, 5, 144, 0, 0, 736, 813, 1, 0, 0, 0, 737, 738, 3, 150, 75, 0, 738, 740, 5, 126, 0, 0, 739, 741, 3, 104, 52, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 144, 0, 0, 743, 744, 1, 0, 0, 0, 744, 745, 5, 64, 0, 0, 745, 746, 5, 126, 0, 0, 746, 747, 3, 88, 44, 0, 747, 748, 5, 144, 0, 0, 748, 813, 1, 0, 0, 0, 749, 750, 3, 150, 75, 0, 750, 752, 5, 126, 0, 0, 751, 753, 3, 104, 52, 0, 752, 751, 1, 0, 0, 0, 752, 753, 1, 0, 0, 0, 753, 754, 1, 0, 0, 0, 754, 755, 5, 144, 0, 0, 755, 756, 1, 0, 0, 0, 756, 757, 5, 64, 0, 0, 757, 758, 3, 150, 75, 0, 758, 813, 1, 0, 0, 0, 759, 765, 3, 150, 75, 0, 760, 762, 5, 126, 0, 0, 761, 763, 3, 104, 52, 0, 762, 761, 1, 0, 0, 0, 762, 763, 1, 0, 0, 0, 763, 764, 1, 0, 0, 0, 764, 766, 5, 144, 0, 0, 765, 760, 1, 0, 0, 0, 765, 766, 1, 0, 0, 0, 766, 767, 1, 0, 0, 0, 767, 769, 5, 126, 0, 0, 768, 770, 5, 23, 0, 0, 769, 768, 1, 0, 0, 0, 769, 770, 1, 0, 0, 0, 770, 772, 1, 0, 0, 0, 771, 773, 3, 108, 54, 0, 772, 771, 1, 0, 0, 0, 772, 773, 1, 0, 0, 0, 773, 774, 1, 0, 0, 0, 774, 775, 5, 144, 0, 0, 775, 813, 1, 0, 0, 0, 776, 813, 3, 114, 57, 0, 777, 813, 3, 158, 79, 0, 778, 813, 3, 140, 70, 0, 779, 780, 5, 114, 0, 0, 780, 813, 3, 106, 53, 19, 781, 782, 5, 56, 0, 0, 782, 813, 3, 106, 53, 13, 783, 784, 3, 130, 65, 0, 784, 785, 5, 116, 0, 0, 785, 787, 1, 0, 0, 0, 786, 783, 1, 0, 0, 0, 786, 787, 1, 0, 0, 0, 787, 788, 1, 0, 0, 0, 788, 813, 5, 108, 0, 0, 789, 790, 5, 126, 0, 0, 790, 791, 3, 34, 17, 0, 791, 792, 5, 144, 0, 0, 792, 813, 1, 0, 0, 0, 793, 794, 5, 126, 0, 0, 794, 795, 3, 106, 53, 0, 795, 796, 5, 144, 0, 0, 796, 813, 1, 0, 0, 0, 797, 798, 5, 126, 0, 0, 798, 799, 3, 104, 52, 0, 799, 800, 5, 144, 0, 0, 800, 813, 1, 0, 0, 0, 801, 803, 5, 125, 0, 0, 802, 804, 3, 104, 52, 0, 803, 802, 1, 0, 0, 0, 803, 804, 1, 0, 0, 0, 804, 805, 1, 0, 0, 0, 805, 813, 5, 143, 0, 0, 806, 808, 5, 124, 0, 0, 807, 809, 3, 30, 15, 0, 808, 807, 1, 0, 0, 0, 808, 809, 1, 0, 0, 0, 809, 810, 1, 0, 0, 0, 810, 813, 5, 142, 0, 0, 811, 813, 3, 122, 61, 0, 812, 683, 1, 0, 0, 0, 812, 703, 1, 0, 0, 0, 812, 710, 1, 0, 0, 0, 812, 712, 1, 0, 0, 0, 812, 716, 1, 0, 0, 0, 812, 727, 1, 0, 0, 0, 812, 729, 1, 0, 0, 0, 812, 737, 1, 0, 0, 0, 812, 749, 1, 0, 0, 0, 812, 759, 1, 0, 0, 0, 812, 776, 1, 0, 0, 0, 812, 777, 1, 0, 0, 0, 812, 778, 1, 0, 0, 0, 812, 779, 1, 0, 0, 0, 812, 781, 1, 0, 0, 0, 812, 786, 1, 0, 0, 0, 812, 789, 1, 0, 0, 0, 812, 793, 1, 0, 0, 0, 812, 797, 1, 0, 0, 0, 812, 801, 1, 0, 0, 0, 812, 806, 1, 0, 0, 0, 812, 811, 1, 0, 0, 0, 813, 907, 1, 0, 0, 0, 814, 818, 10, 18, 0, 0, 815, 819, 5, 108, 0, 0, 816, 819, 5, 146, 0, 0, 817, 819, 5, 133, 0, 0, 818, 815, 1, 0, 0, 0, 818, 816, 1, 0, 0, 0, 818, 817, 1, 0, 0, 0, 819, 820, 1, 0, 0, 0, 820, 906, 3, 106, 53, 19, 821, 825, 10, 17, 0, 0, 822, 826, 5, 134, 0, 0, 823, 826, 5, 114, 0, 0, 824, 826, 5, 113, 0, 0, 825, 822, 1, 0, 0, 0, 825, 823, 1, 0, 0, 0, 825, 824, 1, 0, 0, 0, 826, 827, 1, 0, 0, 0, 827, 906, 3, 106, 53, 18, 828, 853, 10, 16, 0, 0, 829, 854, 5, 117, 0, 0, 830, 854, 5, 118, 0, 0, 831, 854, 5, 129, 0, 0, 832, 854, 5, 127, 0, 0, 833, 854, 5, 128, 0, 0, 834, 854, 5, 119, 0, 0, 835, 854, 5, 120, 0, 0, 836, 838, 5, 56, 0, 0, 837, 836, 1, 0, 0, 0, 837, 838, 1, 0, 0, 0, 838, 839, 1, 0, 0, 0, 839, 841, 5, 40, 0, 0, 840, 842, 5, 14, 0, 0, 841, 840, 1, 0, 0, 0, 841, 842, 1, 0, 0, 0, 842, 854, 1, 0, 0, 0, 843, 845, 5, 56, 0, 0, 844, 843, 1, 0, 0, 0, 844, 845, 1, 0, 0, 0, 845, 846, 1, 0, 0, 0, 846, 854, 7, 10, 0, 0, 847, 854, 5, 140, 0, 0, 848, 854, 5, 141, 0, 0, 849, 854, 5, 131, 0, 0, 850, 854, 5, 122, 0, 0, 851, 854, 5, 123, 0, 0, 852, 854, 5, 130, 0, 0, 853, 829, 1, 0, 0, 0, 853, 830, 1, 0, 0, 0, 853, 831, 1, 0, 0, 0, 853, 832, 1, 0, 0, 0, 853, 833, 1, 0, 0, 0, 853, 834, 1, 0, 0, 0, 853, 835, 1, 0, 0, 0, 853, 837, 1, 0, 0, 0, 853, 844, 1, 0, 0, 0, 853, 847, 1, 0, 0, 0, 853, 848, 1, 0, 0, 0, 853, 849, 1, 0, 0, 0, 853, 850, 1, 0, 0, 0, 853, 851, 1, 0, 0, 0, 853, 852, 1, 0, 0, 0, 854, 855, 1, 0, 0, 0, 855, 906, 3, 106, 53, 17, 856, 857, 10, 14, 0, 0, 857, 858, 5, 132, 0, 0, 858, 906, 3, 106, 53, 15, 859, 860, 10, 12, 0, 0, 860, 861, 5, 2, 0, 0, 861, 906, 3, 106, 53, 13, 862, 863, 10, 11, 0, 0, 863, 864, 5, 61, 0, 0, 864, 906, 3, 106, 53, 12, 865, 867, 10, 10, 0, 0, 866, 868, 5, 56, 0, 0, 867, 866, 1, 0, 0, 0, 867, 868, 1, 0, 0, 0, 868, 869, 1, 0, 0, 0, 869, 870, 5, 9, 0, 0, 870, 871, 3, 106, 53, 0, 871, 872, 5, 2, 0, 0, 872, 873, 3, 106, 53, 11, 873, 906, 1, 0, 0, 0, 874, 875, 10, 9, 0, 0, 875, 876, 5, 135, 0, 0, 876, 877, 3, 106, 53, 0, 877, 878, 5, 111, 0, 0, 878, 879, 3, 106, 53, 9, 879, 906, 1, 0, 0, 0, 880, 881, 10, 22, 0, 0, 881, 882, 5, 125, 0, 0, 882, 883, 3, 106, 53, 0, 883, 884, 5, 143, 0, 0, 884, 906, 1, 0, 0, 0, 885, 886, 10, 21, 0, 0, 886, 887, 5, 116, 0, 0, 887, 906, 5, 104, 0, 0, 888, 889, 10, 20, 0, 0, 889, 890, 5, 116, 0, 0, 890, 906, 3, 150, 75, 0, 891, 892, 10, 15, 0, 0, 892, 894, 5, 44, 0, 0, 893, 895, 5, 56, 0, 0, 894, 893, 1, 0, 0, 0, 894, 895, 1, 0, 0, 0, 895, 896, 1, 0, 0, 0, 896, 906, 5, 57, 0, 0, 897, 903, 10, 8, 0, 0, 898, 904, 3, 148, 74, 0, 899, 900, 5, 6, 0, 0, 900, 904, 3, 150, 75, 0, 901, 902, 5, 6, 0, 0, 902, 904, 5, 106, 0, 0, 903, 898, 1, 0, 0, 0, 903, 899, 1, 0, 0, 0, 903, 901, 1, 0, 0, 0, 904, 906, 1, 0, 0, 0, 905, 814, 1, 0, 0, 0, 905, 821, 1, 0, 0, 0, 905, 828, 1, 0, 0, 0, 905, 856, 1, 0, 0, 0, 905, 859, 1, 0, 0, 0, 905, 862, 1, 0, 0, 0, 905, 865, 1, 0, 0, 0, 905, 874, 1, 0, 0, 0, 905, 880, 1, 0, 0, 0, 905, 885, 1, 0, 0, 0, 905, 888, 1, 0, 0, 0, 905, 891, 1, 0, 0, 0, 905, 897, 1, 0, 0, 0, 906, 909, 1, 0, 0, 0, 907, 905, 1, 0, 0, 0, 907, 908, 1, 0, 0, 0, 908, 107, 1, 0, 0, 0, 909, 907, 1, 0, 0, 0, 910, 915, 3, 110, 55, 0, 911, 912, 5, 112, 0, 0, 912, 914, 3, 110, 55, 0, 913, 911, 1, 0, 0, 0, 914, 917, 1, 0, 0, 0, 915, 913, 1, 0, 0, 0, 915, 916, 1, 0, 0, 0, 916, 109, 1, 0, 0, 0, 917, 915, 1, 0, 0, 0, 918, 921, 3, 112, 56, 0, 919, 921, 3, 106, 53, 0, 920, 918, 1, 0, 0, 0, 920, 919, 1, 0, 0, 0, 921, 111, 1, 0, 0, 0, 922, 923, 5, 126, 0, 0, 923, 928, 3, 150, 75, 0, 924, 925, 5, 112, 0, 0, 925, 927, 3, 150, 75, 0, 926, 924, 1, 0, 0, 0, 927, 930, 1, 0, 0, 0, 928, 926, 1, 0, 0, 0, 928, 929, 1, 0, 0, 0, 929, 931, 1, 0, 0, 0, 930, 928, 1, 0, 0, 0, 931, 932, 5, 144, 0, 0, 932, 942, 1, 0, 0, 0, 933, 938, 3, 150, 75, 0, 934, 935, 5, 112, 0, 0, 935, 937, 3, 150, 75, 0, 936, 934, 1, 0, 0, 0, 937, 940, 1, 0, 0, 0, 938, 936, 1, 0, 0, 0, 938, 939, 1, 0, 0, 0, 939, 942, 1, 0, 0, 0, 940, 938, 1, 0, 0, 0, 941, 922, 1, 0, 0, 0, 941, 933, 1, 0, 0, 0, 942, 943, 1, 0, 0, 0, 943, 944, 5, 107, 0, 0, 944, 945, 3, 106, 53, 0, 945, 113, 1, 0, 0, 0, 946, 947, 5, 128, 0, 0, 947, 951, 3, 150, 75, 0, 948, 950, 3, 116, 58, 0, 949, 948, 1, 0, 0, 0, 950, 953, 1, 0, 0, 0, 951, 949, 1, 0, 0, 0, 951, 952, 1, 0, 0, 0, 952, 954, 1, 0, 0, 0, 953, 951, 1, 0, 0, 0, 954, 955, 5, 146, 0, 0, 955, 956, 5, 120, 0, 0, 956, 975, 1, 0, 0, 0, 957, 958, 5, 128, 0, 0, 958, 962, 3, 150, 75, 0, 959, 961, 3, 116, 58, 0, 960, 959, 1, 0, 0, 0, 961, 964, 1, 0, 0, 0, 962, 960, 1, 0, 0, 0, 962, 963, 1, 0, 0, 0, 963, 965, 1, 0, 0, 0, 964, 962, 1, 0, 0, 0, 965, 967, 5, 120, 0, 0, 966, 968, 3, 114, 57, 0, 967, 966, 1, 0, 0, 0, 967, 968, 1, 0, 0, 0, 968, 969, 1, 0, 0, 0, 969, 970, 5, 128, 0, 0, 970, 971, 5, 146, 0, 0, 971, 972, 3, 150, 75, 0, 972, 973, 5, 120, 0, 0, 973, 975, 1, 0, 0, 0, 974, 946, 1, 0, 0, 0, 974, 957, 1, 0, 0, 0, 975, 115, 1, 0, 0, 0, 976, 977, 3, 150, 75, 0, 977, 978, 5, 118, 0, 0, 978, 979, 3, 156, 78, 0, 979, 988, 1, 0, 0, 0, 980, 981, 3, 150, 75, 0, 981, 982, 5, 118, 0, 0, 982, 983, 5, 124, 0, 0, 983, 984, 3, 106, 53, 0, 984, 985, 5, 142, 0, 0, 985, 988, 1, 0, 0, 0, 986, 988, 3, 150, 75, 0, 987, 976, 1, 0, 0, 0, 987, 980, 1, 0, 0, 0, 987, 986, 1, 0, 0, 0, 988, 117, 1, 0, 0, 0, 989, 994, 3, 120, 60, 0, 990, 991, 5, 112, 0, 0, 991, 993, 3, 120, 60, 0, 992, 990, 1, 0, 0, 0, 993, 996, 1, 0, 0, 0, 994, 992, 1, 0, 0, 0, 994, 995, 1, 0, 0, 0, 995, 119, 1, 0, 0, 0, 996, 994, 1, 0, 0, 0, 997, 998, 3, 150, 75, 0, 998, 999, 5, 6, 0, 0, 999, 1000, 5, 126, 0, 0, 1000, 1001, 3, 34, 17, 0, 1001, 1002, 5, 144, 0, 0, 1002, 1008, 1, 0, 0, 0, 1003, 1004, 3, 106, 53, 0, 1004, 1005, 5, 6, 0, 0, 1005, 1006, 3, 150, 75, 0, 1006, 1008, 1, 0, 0, 0, 1007, 997, 1, 0, 0, 0, 1007, 1003, 1, 0, 0, 0, 1008, 121, 1, 0, 0, 0, 1009, 1017, 3, 154, 77, 0, 1010, 1011, 3, 130, 65, 0, 1011, 1012, 5, 116, 0, 0, 1012, 1014, 1, 0, 0, 0, 1013, 1010, 1, 0, 0, 0, 1013, 1014, 1, 0, 0, 0, 1014, 1015, 1, 0, 0, 0, 1015, 1017, 3, 124, 62, 0, 1016, 1009, 1, 0, 0, 0, 1016, 1013, 1, 0, 0, 0, 1017, 123, 1, 0, 0, 0, 1018, 1023, 3, 150, 75, 0, 1019, 1020, 5, 116, 0, 0, 1020, 1022, 3, 150, 75, 0, 1021, 1019, 1, 0, 0, 0, 1022, 1025, 1, 0, 0, 0, 1023, 1021, 1, 0, 0, 0, 1023, 1024, 1, 0, 0, 0, 1024, 125, 1, 0, 0, 0, 1025, 1023, 1, 0, 0, 0, 1026, 1027, 6, 63, -1, 0, 1027, 1036, 3, 130, 65, 0, 1028, 1036, 3, 128, 64, 0, 1029, 1030, 5, 126, 0, 0, 1030, 1031, 3, 34, 17, 0, 1031, 1032, 5, 144, 0, 0, 1032, 1036, 1, 0, 0, 0, 1033, 1036, 3, 114, 57, 0, 1034, 1036, 3, 154, 77, 0, 1035, 1026, 1, 0, 0, 0, 1035, 1028, 1, 0, 0, 0, 1035, 1029, 1, 0, 0, 0, 1035, 1033, 1, 0, 0, 0, 1035, 1034, 1, 0, 0, 0, 1036, 1045, 1, 0, 0, 0, 1037, 1041, 10, 3, 0, 0, 1038, 1042, 3, 148, 74, 0, 1039, 1040, 5, 6, 0, 0, 1040, 1042, 3, 150, 75, 0, 1041, 1038, 1, 0, 0, 0, 1041, 1039, 1, 0, 0, 0, 1042, 1044, 1, 0, 0, 0, 1043, 1037, 1, 0, 0, 0, 1044, 1047, 1, 0, 0, 0, 1045, 1043, 1, 0, 0, 0, 1045, 1046, 1, 0, 0, 0, 1046, 127, 1, 0, 0, 0, 1047, 1045, 1, 0, 0, 0, 1048, 1049, 3, 150, 75, 0, 1049, 1051, 5, 126, 0, 0, 1050, 1052, 3, 132, 66, 0, 1051, 1050, 1, 0, 0, 0, 1051, 1052, 1, 0, 0, 0, 1052, 1053, 1, 0, 0, 0, 1053, 1054, 5, 144, 0, 0, 1054, 129, 1, 0, 0, 0, 1055, 1056, 3, 134, 67, 0, 1056, 1057, 5, 116, 0, 0, 1057, 1059, 1, 0, 0, 0, 1058, 1055, 1, 0, 0, 0, 1058, 1059, 1, 0, 0, 0, 1059, 1060, 1, 0, 0, 0, 1060, 1061, 3, 150, 75, 0, 1061, 131, 1, 0, 0, 0, 1062, 1067, 3, 106, 53, 0, 1063, 1064, 5, 112, 0, 0, 1064, 1066, 3, 106, 53, 0, 1065, 1063, 1, 0, 0, 0, 1066, 1069, 1, 0, 0, 0, 1067, 1065, 1, 0, 0, 0, 1067, 1068, 1, 0, 0, 0, 1068, 133, 1, 0, 0, 0, 1069, 1067, 1, 0, 0, 0, 1070, 1071, 3, 150, 75, 0, 1071, 135, 1, 0, 0, 0, 1072, 1081, 5, 102, 0, 0, 1073, 1074, 5, 116, 0, 0, 1074, 1081, 7, 11, 0, 0, 1075, 1076, 5, 104, 0, 0, 1076, 1078, 5, 116, 0, 0, 1077, 1079, 7, 11, 0, 0, 1078, 1077, 1, 0, 0, 0, 1078, 1079, 1, 0, 0, 0, 1079, 1081, 1, 0, 0, 0, 1080, 1072, 1, 0, 0, 0, 1080, 1073, 1, 0, 0, 0, 1080, 1075, 1, 0, 0, 0, 1081, 137, 1, 0, 0, 0, 1082, 1084, 7, 12, 0, 0, 1083, 1082, 1, 0, 0, 0, 1083, 1084, 1, 0, 0, 0, 1084, 1091, 1, 0, 0, 0, 1085, 1092, 3, 136, 68, 0, 1086, 1092, 5, 103, 0, 0, 1087, 1092, 5, 104, 0, 0, 1088, 1092, 5, 105, 0, 0, 1089, 1092, 5, 41, 0, 0, 1090, 1092, 5, 55, 0, 0, 1091, 1085, 1, 0, 0, 0, 1091, 1086, 1, 0, 0, 0, 1091, 1087, 1, 0, 0, 0, 1091, 1088, 1, 0, 0, 0, 1091, 1089, 1, 0, 0, 0, 1091, 1090, 1, 0, 0, 0, 1092, 139, 1, 0, 0, 0, 1093, 1097, 3, 138, 69, 0, 1094, 1097, 5, 106, 0, 0, 1095, 1097, 5, 57, 0, 0, 1096, 1093, 1, 0, 0, 0, 1096, 1094, 1, 0, 0, 0, 1096, 1095, 1, 0, 0, 0, 1097, 141, 1, 0, 0, 0, 1098, 1099, 7, 13, 0, 0, 1099, 143, 1, 0, 0, 0, 1100, 1101, 7, 14, 0, 0, 1101, 145, 1, 0, 0, 0, 1102, 1103, 7, 15, 0, 0, 1103, 147, 1, 0, 0, 0, 1104, 1107, 5, 101, 0, 0, 1105, 1107, 3, 146, 73, 0, 1106, 1104, 1, 0, 0, 0, 1106, 1105, 1, 0, 0, 0, 1107, 149, 1, 0, 0, 0, 1108, 1112, 5, 101, 0, 0, 1109, 1112, 3, 142, 71, 0, 1110, 1112, 3, 144, 72, 0, 1111, 1108, 1, 0, 0, 0, 1111, 1109, 1, 0, 0, 0, 1111, 1110, 1, 0, 0, 0, 1112, 151, 1, 0, 0, 0, 1113, 1114, 3, 156, 78, 0, 1114, 1115, 5, 118, 0, 0, 1115, 1116, 3, 138, 69, 0, 1116, 153, 1, 0, 0, 0, 1117, 1118, 5, 124, 0, 0, 1118, 1119, 3, 150, 75, 0, 1119, 1120, 5, 142, 0, 0, 1120, 155, 1, 0, 0, 0, 1121, 1124, 5, 106, 0, 0, 1122, 1124, 3, 158, 79, 0, 1123, 1121, 1, 0, 0, 0, 1123, 1122, 1, 0, 0, 0, 1124, 157, 1, 0, 0, 0, 1125, 1129, 5, 137, 0, 0, 1126, 1128, 3, 160, 80, 0, 1127, 1126, 1, 0, 0, 0, 1128, 1131, 1, 0, 0, 0, 1129, 1127, 1, 0, 0, 0, 1129, 1130, 1, 0, 0, 0, 1130, 1132, 1, 0, 0, 0, 1131, 1129, 1, 0, 0, 0, 1132, 1133, 5, 139, 0, 0, 1133, 159, 1, 0, 0, 0, 1134, 1135, 5, 152, 0, 0, 1135, 1136, 3, 106, 53, 0, 1136, 1137, 5, 142, 0, 0, 1137, 1140, 1, 0, 0, 0, 1138, 1140, 5, 151, 0, 0, 1139, 1134, 1, 0, 0, 0, 1139, 1138, 1, 0, 0, 0, 1140, 161, 1, 0, 0, 0, 1141, 1145, 5, 138, 0, 0, 1142, 1144, 3, 164, 82, 0, 1143, 1142, 1, 0, 0, 0, 1144, 1147, 1, 0, 0, 0, 1145, 1143, 1, 0, 0, 0, 1145, 1146, 1, 0, 0, 0, 1146, 1148, 1, 0, 0, 0, 1147, 1145, 1, 0, 0, 0, 1148, 1149, 5, 0, 0, 1, 1149, 163, 1, 0, 0, 0, 1150, 1151, 5, 154, 0, 0, 1151, 1152, 3, 106, 53, 0, 1152, 1153, 5, 142, 0, 0, 1153, 1156, 1, 0, 0, 0, 1154, 1156, 5, 153, 0, 0, 1155, 1150, 1, 0, 0, 0, 1155, 1154, 1, 0, 0, 0, 1156, 165, 1, 0, 0, 0, 135, 169, 176, 185, 200, 212, 224, 240, 251, 265, 271, 281, 290, 293, 297, 300, 304, 307, 310, 313, 316, 320, 324, 327, 330, 333, 337, 340, 349, 355, 376, 393, 410, 416, 422, 433, 435, 446, 449, 455, 463, 469, 471, 475, 480, 483, 486, 490, 494, 497, 499, 502, 506, 510, 513, 515, 517, 522, 533, 539, 546, 551, 555, 559, 565, 567, 574, 582, 585, 588, 607, 621, 637, 649, 661, 669, 673, 680, 686, 695, 699, 723, 740, 752, 762, 765, 769, 772, 786, 803, 808, 812, 818, 825, 837, 841, 844, 853, 867, 894, 903, 905, 907, 915, 920, 928, 938, 941, 951, 962, 967, 974, 987, 994, 1007, 1013, 1016, 1023, 1035, 1041, 1045, 1051, 1058, 1067, 1078, 1080, 1083, 1091, 1096, 1106, 1111, 1123, 1129, 1139, 1145, 1155] \ No newline at end of file +[4, 1, 154, 1178, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 1, 0, 5, 0, 168, 8, 0, 10, 0, 12, 0, 171, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 3, 1, 177, 8, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 186, 8, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 5, 5, 199, 8, 5, 10, 5, 12, 5, 202, 9, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 213, 8, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 225, 8, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 241, 8, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 5, 13, 250, 8, 13, 10, 13, 12, 13, 253, 9, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 5, 15, 264, 8, 15, 10, 15, 12, 15, 267, 9, 15, 1, 16, 1, 16, 1, 16, 3, 16, 272, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 5, 17, 280, 8, 17, 10, 17, 12, 17, 283, 9, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 3, 18, 291, 8, 18, 1, 19, 3, 19, 294, 8, 19, 1, 19, 1, 19, 3, 19, 298, 8, 19, 1, 19, 3, 19, 301, 8, 19, 1, 19, 1, 19, 3, 19, 305, 8, 19, 1, 19, 3, 19, 308, 8, 19, 1, 19, 3, 19, 311, 8, 19, 1, 19, 3, 19, 314, 8, 19, 1, 19, 3, 19, 317, 8, 19, 1, 19, 1, 19, 3, 19, 321, 8, 19, 1, 19, 1, 19, 3, 19, 325, 8, 19, 1, 19, 3, 19, 328, 8, 19, 1, 19, 3, 19, 331, 8, 19, 1, 19, 3, 19, 334, 8, 19, 1, 19, 1, 19, 3, 19, 338, 8, 19, 1, 19, 3, 19, 341, 8, 19, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 350, 8, 21, 1, 22, 1, 22, 1, 22, 1, 23, 3, 23, 356, 8, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 1, 24, 5, 24, 375, 8, 24, 10, 24, 12, 24, 378, 9, 24, 1, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 3, 27, 394, 8, 27, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 411, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 417, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 423, 8, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 3, 31, 434, 8, 31, 3, 31, 436, 8, 31, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 3, 34, 447, 8, 34, 1, 34, 3, 34, 450, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 456, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, 464, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 470, 8, 34, 10, 34, 12, 34, 473, 9, 34, 1, 35, 3, 35, 476, 8, 35, 1, 35, 1, 35, 1, 35, 3, 35, 481, 8, 35, 1, 35, 3, 35, 484, 8, 35, 1, 35, 3, 35, 487, 8, 35, 1, 35, 1, 35, 3, 35, 491, 8, 35, 1, 35, 1, 35, 3, 35, 495, 8, 35, 1, 35, 3, 35, 498, 8, 35, 3, 35, 500, 8, 35, 1, 35, 3, 35, 503, 8, 35, 1, 35, 1, 35, 3, 35, 507, 8, 35, 1, 35, 1, 35, 3, 35, 511, 8, 35, 1, 35, 3, 35, 514, 8, 35, 3, 35, 516, 8, 35, 3, 35, 518, 8, 35, 1, 36, 1, 36, 1, 36, 3, 36, 523, 8, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 3, 37, 534, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 540, 8, 38, 1, 39, 1, 39, 1, 39, 5, 39, 545, 8, 39, 10, 39, 12, 39, 548, 9, 39, 1, 40, 1, 40, 3, 40, 552, 8, 40, 1, 40, 1, 40, 3, 40, 556, 8, 40, 1, 40, 1, 40, 3, 40, 560, 8, 40, 1, 41, 1, 41, 1, 41, 1, 41, 3, 41, 566, 8, 41, 3, 41, 568, 8, 41, 1, 42, 1, 42, 1, 42, 5, 42, 573, 8, 42, 10, 42, 12, 42, 576, 9, 42, 1, 43, 1, 43, 1, 43, 1, 43, 1, 44, 3, 44, 583, 8, 44, 1, 44, 3, 44, 586, 8, 44, 1, 44, 3, 44, 589, 8, 44, 1, 45, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 1, 48, 3, 48, 608, 8, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 1, 49, 3, 49, 622, 8, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 636, 8, 51, 10, 51, 12, 51, 639, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 648, 8, 51, 10, 51, 12, 51, 651, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 5, 51, 660, 8, 51, 10, 51, 12, 51, 663, 9, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 3, 51, 670, 8, 51, 1, 51, 1, 51, 3, 51, 674, 8, 51, 1, 52, 1, 52, 1, 52, 5, 52, 679, 8, 52, 10, 52, 12, 52, 682, 9, 52, 1, 53, 1, 53, 1, 53, 3, 53, 687, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 4, 53, 694, 8, 53, 11, 53, 12, 53, 695, 1, 53, 1, 53, 3, 53, 700, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 724, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 741, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 747, 8, 53, 1, 53, 3, 53, 750, 8, 53, 1, 53, 3, 53, 753, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 763, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 769, 8, 53, 1, 53, 3, 53, 772, 8, 53, 1, 53, 3, 53, 775, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 783, 8, 53, 1, 53, 3, 53, 786, 8, 53, 1, 53, 1, 53, 3, 53, 790, 8, 53, 1, 53, 3, 53, 793, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 807, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 824, 8, 53, 1, 53, 1, 53, 1, 53, 3, 53, 829, 8, 53, 1, 53, 1, 53, 3, 53, 833, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 839, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 846, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 858, 8, 53, 1, 53, 1, 53, 3, 53, 862, 8, 53, 1, 53, 3, 53, 865, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 874, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 888, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 915, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 924, 8, 53, 5, 53, 926, 8, 53, 10, 53, 12, 53, 929, 9, 53, 1, 54, 1, 54, 1, 54, 5, 54, 934, 8, 54, 10, 54, 12, 54, 937, 9, 54, 1, 55, 1, 55, 3, 55, 941, 8, 55, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 947, 8, 56, 10, 56, 12, 56, 950, 9, 56, 1, 56, 1, 56, 1, 56, 1, 56, 1, 56, 5, 56, 957, 8, 56, 10, 56, 12, 56, 960, 9, 56, 3, 56, 962, 8, 56, 1, 56, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 5, 57, 970, 8, 57, 10, 57, 12, 57, 973, 9, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 5, 57, 981, 8, 57, 10, 57, 12, 57, 984, 9, 57, 1, 57, 1, 57, 3, 57, 988, 8, 57, 1, 57, 1, 57, 1, 57, 1, 57, 1, 57, 3, 57, 995, 8, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 3, 58, 1008, 8, 58, 1, 59, 1, 59, 1, 59, 5, 59, 1013, 8, 59, 10, 59, 12, 59, 1016, 9, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 1, 60, 3, 60, 1028, 8, 60, 1, 61, 1, 61, 1, 61, 1, 61, 3, 61, 1034, 8, 61, 1, 61, 3, 61, 1037, 8, 61, 1, 62, 1, 62, 1, 62, 5, 62, 1042, 8, 62, 10, 62, 12, 62, 1045, 9, 62, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1056, 8, 63, 1, 63, 1, 63, 1, 63, 1, 63, 3, 63, 1062, 8, 63, 5, 63, 1064, 8, 63, 10, 63, 12, 63, 1067, 9, 63, 1, 64, 1, 64, 1, 64, 3, 64, 1072, 8, 64, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 3, 65, 1079, 8, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 66, 5, 66, 1086, 8, 66, 10, 66, 12, 66, 1089, 9, 66, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 1, 68, 3, 68, 1099, 8, 68, 3, 68, 1101, 8, 68, 1, 69, 3, 69, 1104, 8, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 1, 69, 3, 69, 1112, 8, 69, 1, 70, 1, 70, 1, 70, 3, 70, 1117, 8, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 3, 74, 1127, 8, 74, 1, 75, 1, 75, 1, 75, 3, 75, 1132, 8, 75, 1, 76, 1, 76, 1, 76, 1, 76, 1, 77, 1, 77, 1, 77, 1, 77, 1, 78, 1, 78, 3, 78, 1144, 8, 78, 1, 79, 1, 79, 5, 79, 1148, 8, 79, 10, 79, 12, 79, 1151, 9, 79, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 3, 80, 1160, 8, 80, 1, 81, 1, 81, 5, 81, 1164, 8, 81, 10, 81, 12, 81, 1167, 9, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 3, 82, 1176, 8, 82, 1, 82, 0, 3, 68, 106, 126, 83, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 0, 16, 2, 0, 17, 17, 72, 72, 2, 0, 42, 42, 49, 49, 3, 0, 1, 1, 4, 4, 8, 8, 4, 0, 1, 1, 3, 4, 8, 8, 78, 78, 2, 0, 49, 49, 71, 71, 2, 0, 1, 1, 4, 4, 2, 0, 7, 7, 21, 22, 2, 0, 28, 28, 47, 47, 2, 0, 69, 69, 74, 74, 3, 0, 10, 10, 48, 48, 87, 87, 2, 0, 39, 39, 51, 51, 1, 0, 103, 104, 2, 0, 114, 114, 134, 134, 7, 0, 20, 20, 36, 36, 53, 54, 68, 68, 76, 76, 93, 93, 99, 99, 12, 0, 1, 19, 21, 28, 30, 35, 37, 40, 42, 49, 51, 52, 56, 56, 58, 67, 69, 75, 77, 92, 94, 95, 97, 98, 4, 0, 19, 19, 28, 28, 37, 37, 46, 46, 1314, 0, 169, 1, 0, 0, 0, 2, 176, 1, 0, 0, 0, 4, 178, 1, 0, 0, 0, 6, 180, 1, 0, 0, 0, 8, 189, 1, 0, 0, 0, 10, 195, 1, 0, 0, 0, 12, 212, 1, 0, 0, 0, 14, 214, 1, 0, 0, 0, 16, 217, 1, 0, 0, 0, 18, 226, 1, 0, 0, 0, 20, 232, 1, 0, 0, 0, 22, 236, 1, 0, 0, 0, 24, 245, 1, 0, 0, 0, 26, 247, 1, 0, 0, 0, 28, 256, 1, 0, 0, 0, 30, 260, 1, 0, 0, 0, 32, 271, 1, 0, 0, 0, 34, 275, 1, 0, 0, 0, 36, 290, 1, 0, 0, 0, 38, 293, 1, 0, 0, 0, 40, 342, 1, 0, 0, 0, 42, 345, 1, 0, 0, 0, 44, 351, 1, 0, 0, 0, 46, 355, 1, 0, 0, 0, 48, 361, 1, 0, 0, 0, 50, 379, 1, 0, 0, 0, 52, 382, 1, 0, 0, 0, 54, 385, 1, 0, 0, 0, 56, 395, 1, 0, 0, 0, 58, 398, 1, 0, 0, 0, 60, 402, 1, 0, 0, 0, 62, 435, 1, 0, 0, 0, 64, 437, 1, 0, 0, 0, 66, 440, 1, 0, 0, 0, 68, 455, 1, 0, 0, 0, 70, 517, 1, 0, 0, 0, 72, 522, 1, 0, 0, 0, 74, 533, 1, 0, 0, 0, 76, 535, 1, 0, 0, 0, 78, 541, 1, 0, 0, 0, 80, 549, 1, 0, 0, 0, 82, 567, 1, 0, 0, 0, 84, 569, 1, 0, 0, 0, 86, 577, 1, 0, 0, 0, 88, 582, 1, 0, 0, 0, 90, 590, 1, 0, 0, 0, 92, 594, 1, 0, 0, 0, 94, 598, 1, 0, 0, 0, 96, 607, 1, 0, 0, 0, 98, 621, 1, 0, 0, 0, 100, 623, 1, 0, 0, 0, 102, 673, 1, 0, 0, 0, 104, 675, 1, 0, 0, 0, 106, 832, 1, 0, 0, 0, 108, 930, 1, 0, 0, 0, 110, 940, 1, 0, 0, 0, 112, 961, 1, 0, 0, 0, 114, 994, 1, 0, 0, 0, 116, 1007, 1, 0, 0, 0, 118, 1009, 1, 0, 0, 0, 120, 1027, 1, 0, 0, 0, 122, 1036, 1, 0, 0, 0, 124, 1038, 1, 0, 0, 0, 126, 1055, 1, 0, 0, 0, 128, 1068, 1, 0, 0, 0, 130, 1078, 1, 0, 0, 0, 132, 1082, 1, 0, 0, 0, 134, 1090, 1, 0, 0, 0, 136, 1100, 1, 0, 0, 0, 138, 1103, 1, 0, 0, 0, 140, 1116, 1, 0, 0, 0, 142, 1118, 1, 0, 0, 0, 144, 1120, 1, 0, 0, 0, 146, 1122, 1, 0, 0, 0, 148, 1126, 1, 0, 0, 0, 150, 1131, 1, 0, 0, 0, 152, 1133, 1, 0, 0, 0, 154, 1137, 1, 0, 0, 0, 156, 1143, 1, 0, 0, 0, 158, 1145, 1, 0, 0, 0, 160, 1159, 1, 0, 0, 0, 162, 1161, 1, 0, 0, 0, 164, 1175, 1, 0, 0, 0, 166, 168, 3, 2, 1, 0, 167, 166, 1, 0, 0, 0, 168, 171, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 169, 170, 1, 0, 0, 0, 170, 172, 1, 0, 0, 0, 171, 169, 1, 0, 0, 0, 172, 173, 5, 0, 0, 1, 173, 1, 1, 0, 0, 0, 174, 177, 3, 6, 3, 0, 175, 177, 3, 12, 6, 0, 176, 174, 1, 0, 0, 0, 176, 175, 1, 0, 0, 0, 177, 3, 1, 0, 0, 0, 178, 179, 3, 106, 53, 0, 179, 5, 1, 0, 0, 0, 180, 181, 5, 50, 0, 0, 181, 185, 3, 150, 75, 0, 182, 183, 5, 111, 0, 0, 183, 184, 5, 118, 0, 0, 184, 186, 3, 4, 2, 0, 185, 182, 1, 0, 0, 0, 185, 186, 1, 0, 0, 0, 186, 187, 1, 0, 0, 0, 187, 188, 5, 145, 0, 0, 188, 7, 1, 0, 0, 0, 189, 190, 3, 4, 2, 0, 190, 191, 5, 111, 0, 0, 191, 192, 5, 118, 0, 0, 192, 193, 3, 4, 2, 0, 193, 194, 5, 145, 0, 0, 194, 9, 1, 0, 0, 0, 195, 200, 3, 150, 75, 0, 196, 197, 5, 112, 0, 0, 197, 199, 3, 150, 75, 0, 198, 196, 1, 0, 0, 0, 199, 202, 1, 0, 0, 0, 200, 198, 1, 0, 0, 0, 200, 201, 1, 0, 0, 0, 201, 11, 1, 0, 0, 0, 202, 200, 1, 0, 0, 0, 203, 213, 3, 20, 10, 0, 204, 213, 3, 24, 12, 0, 205, 213, 3, 14, 7, 0, 206, 213, 3, 16, 8, 0, 207, 213, 3, 18, 9, 0, 208, 213, 3, 22, 11, 0, 209, 213, 3, 8, 4, 0, 210, 213, 3, 20, 10, 0, 211, 213, 3, 26, 13, 0, 212, 203, 1, 0, 0, 0, 212, 204, 1, 0, 0, 0, 212, 205, 1, 0, 0, 0, 212, 206, 1, 0, 0, 0, 212, 207, 1, 0, 0, 0, 212, 208, 1, 0, 0, 0, 212, 209, 1, 0, 0, 0, 212, 210, 1, 0, 0, 0, 212, 211, 1, 0, 0, 0, 213, 13, 1, 0, 0, 0, 214, 215, 3, 4, 2, 0, 215, 216, 5, 145, 0, 0, 216, 15, 1, 0, 0, 0, 217, 218, 5, 38, 0, 0, 218, 219, 5, 126, 0, 0, 219, 220, 3, 4, 2, 0, 220, 221, 5, 144, 0, 0, 221, 224, 3, 12, 6, 0, 222, 223, 5, 24, 0, 0, 223, 225, 3, 12, 6, 0, 224, 222, 1, 0, 0, 0, 224, 225, 1, 0, 0, 0, 225, 17, 1, 0, 0, 0, 226, 227, 5, 96, 0, 0, 227, 228, 5, 126, 0, 0, 228, 229, 3, 4, 2, 0, 229, 230, 5, 144, 0, 0, 230, 231, 3, 12, 6, 0, 231, 19, 1, 0, 0, 0, 232, 233, 5, 70, 0, 0, 233, 234, 3, 4, 2, 0, 234, 235, 5, 145, 0, 0, 235, 21, 1, 0, 0, 0, 236, 237, 5, 29, 0, 0, 237, 238, 3, 150, 75, 0, 238, 240, 5, 126, 0, 0, 239, 241, 3, 10, 5, 0, 240, 239, 1, 0, 0, 0, 240, 241, 1, 0, 0, 0, 241, 242, 1, 0, 0, 0, 242, 243, 5, 144, 0, 0, 243, 244, 3, 26, 13, 0, 244, 23, 1, 0, 0, 0, 245, 246, 5, 145, 0, 0, 246, 25, 1, 0, 0, 0, 247, 251, 5, 124, 0, 0, 248, 250, 3, 2, 1, 0, 249, 248, 1, 0, 0, 0, 250, 253, 1, 0, 0, 0, 251, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, 254, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 254, 255, 5, 142, 0, 0, 255, 27, 1, 0, 0, 0, 256, 257, 3, 4, 2, 0, 257, 258, 5, 111, 0, 0, 258, 259, 3, 4, 2, 0, 259, 29, 1, 0, 0, 0, 260, 265, 3, 28, 14, 0, 261, 262, 5, 112, 0, 0, 262, 264, 3, 28, 14, 0, 263, 261, 1, 0, 0, 0, 264, 267, 1, 0, 0, 0, 265, 263, 1, 0, 0, 0, 265, 266, 1, 0, 0, 0, 266, 31, 1, 0, 0, 0, 267, 265, 1, 0, 0, 0, 268, 272, 3, 34, 17, 0, 269, 272, 3, 38, 19, 0, 270, 272, 3, 114, 57, 0, 271, 268, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 271, 270, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 274, 5, 0, 0, 1, 274, 33, 1, 0, 0, 0, 275, 281, 3, 36, 18, 0, 276, 277, 5, 91, 0, 0, 277, 278, 5, 1, 0, 0, 278, 280, 3, 36, 18, 0, 279, 276, 1, 0, 0, 0, 280, 283, 1, 0, 0, 0, 281, 279, 1, 0, 0, 0, 281, 282, 1, 0, 0, 0, 282, 35, 1, 0, 0, 0, 283, 281, 1, 0, 0, 0, 284, 291, 3, 38, 19, 0, 285, 286, 5, 126, 0, 0, 286, 287, 3, 34, 17, 0, 287, 288, 5, 144, 0, 0, 288, 291, 1, 0, 0, 0, 289, 291, 3, 154, 77, 0, 290, 284, 1, 0, 0, 0, 290, 285, 1, 0, 0, 0, 290, 289, 1, 0, 0, 0, 291, 37, 1, 0, 0, 0, 292, 294, 3, 40, 20, 0, 293, 292, 1, 0, 0, 0, 293, 294, 1, 0, 0, 0, 294, 295, 1, 0, 0, 0, 295, 297, 5, 77, 0, 0, 296, 298, 5, 23, 0, 0, 297, 296, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 300, 1, 0, 0, 0, 299, 301, 3, 42, 21, 0, 300, 299, 1, 0, 0, 0, 300, 301, 1, 0, 0, 0, 301, 302, 1, 0, 0, 0, 302, 304, 3, 104, 52, 0, 303, 305, 3, 44, 22, 0, 304, 303, 1, 0, 0, 0, 304, 305, 1, 0, 0, 0, 305, 307, 1, 0, 0, 0, 306, 308, 3, 46, 23, 0, 307, 306, 1, 0, 0, 0, 307, 308, 1, 0, 0, 0, 308, 310, 1, 0, 0, 0, 309, 311, 3, 50, 25, 0, 310, 309, 1, 0, 0, 0, 310, 311, 1, 0, 0, 0, 311, 313, 1, 0, 0, 0, 312, 314, 3, 52, 26, 0, 313, 312, 1, 0, 0, 0, 313, 314, 1, 0, 0, 0, 314, 316, 1, 0, 0, 0, 315, 317, 3, 54, 27, 0, 316, 315, 1, 0, 0, 0, 316, 317, 1, 0, 0, 0, 317, 320, 1, 0, 0, 0, 318, 319, 5, 98, 0, 0, 319, 321, 7, 0, 0, 0, 320, 318, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 324, 1, 0, 0, 0, 322, 323, 5, 98, 0, 0, 323, 325, 5, 86, 0, 0, 324, 322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 327, 1, 0, 0, 0, 326, 328, 3, 56, 28, 0, 327, 326, 1, 0, 0, 0, 327, 328, 1, 0, 0, 0, 328, 330, 1, 0, 0, 0, 329, 331, 3, 48, 24, 0, 330, 329, 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 333, 1, 0, 0, 0, 332, 334, 3, 58, 29, 0, 333, 332, 1, 0, 0, 0, 333, 334, 1, 0, 0, 0, 334, 337, 1, 0, 0, 0, 335, 338, 3, 62, 31, 0, 336, 338, 3, 64, 32, 0, 337, 335, 1, 0, 0, 0, 337, 336, 1, 0, 0, 0, 337, 338, 1, 0, 0, 0, 338, 340, 1, 0, 0, 0, 339, 341, 3, 66, 33, 0, 340, 339, 1, 0, 0, 0, 340, 341, 1, 0, 0, 0, 341, 39, 1, 0, 0, 0, 342, 343, 5, 98, 0, 0, 343, 344, 3, 118, 59, 0, 344, 41, 1, 0, 0, 0, 345, 346, 5, 85, 0, 0, 346, 349, 5, 104, 0, 0, 347, 348, 5, 98, 0, 0, 348, 350, 5, 82, 0, 0, 349, 347, 1, 0, 0, 0, 349, 350, 1, 0, 0, 0, 350, 43, 1, 0, 0, 0, 351, 352, 5, 32, 0, 0, 352, 353, 3, 68, 34, 0, 353, 45, 1, 0, 0, 0, 354, 356, 7, 1, 0, 0, 355, 354, 1, 0, 0, 0, 355, 356, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 358, 5, 5, 0, 0, 358, 359, 5, 45, 0, 0, 359, 360, 3, 104, 52, 0, 360, 47, 1, 0, 0, 0, 361, 362, 5, 97, 0, 0, 362, 363, 3, 150, 75, 0, 363, 364, 5, 6, 0, 0, 364, 365, 5, 126, 0, 0, 365, 366, 3, 88, 44, 0, 366, 376, 5, 144, 0, 0, 367, 368, 5, 112, 0, 0, 368, 369, 3, 150, 75, 0, 369, 370, 5, 6, 0, 0, 370, 371, 5, 126, 0, 0, 371, 372, 3, 88, 44, 0, 372, 373, 5, 144, 0, 0, 373, 375, 1, 0, 0, 0, 374, 367, 1, 0, 0, 0, 375, 378, 1, 0, 0, 0, 376, 374, 1, 0, 0, 0, 376, 377, 1, 0, 0, 0, 377, 49, 1, 0, 0, 0, 378, 376, 1, 0, 0, 0, 379, 380, 5, 67, 0, 0, 380, 381, 3, 106, 53, 0, 381, 51, 1, 0, 0, 0, 382, 383, 5, 95, 0, 0, 383, 384, 3, 106, 53, 0, 384, 53, 1, 0, 0, 0, 385, 386, 5, 34, 0, 0, 386, 393, 5, 11, 0, 0, 387, 388, 7, 0, 0, 0, 388, 389, 5, 126, 0, 0, 389, 390, 3, 104, 52, 0, 390, 391, 5, 144, 0, 0, 391, 394, 1, 0, 0, 0, 392, 394, 3, 104, 52, 0, 393, 387, 1, 0, 0, 0, 393, 392, 1, 0, 0, 0, 394, 55, 1, 0, 0, 0, 395, 396, 5, 35, 0, 0, 396, 397, 3, 106, 53, 0, 397, 57, 1, 0, 0, 0, 398, 399, 5, 62, 0, 0, 399, 400, 5, 11, 0, 0, 400, 401, 3, 78, 39, 0, 401, 59, 1, 0, 0, 0, 402, 403, 5, 62, 0, 0, 403, 404, 5, 11, 0, 0, 404, 405, 3, 104, 52, 0, 405, 61, 1, 0, 0, 0, 406, 407, 5, 52, 0, 0, 407, 410, 3, 106, 53, 0, 408, 409, 5, 112, 0, 0, 409, 411, 3, 106, 53, 0, 410, 408, 1, 0, 0, 0, 410, 411, 1, 0, 0, 0, 411, 416, 1, 0, 0, 0, 412, 413, 5, 98, 0, 0, 413, 417, 5, 82, 0, 0, 414, 415, 5, 11, 0, 0, 415, 417, 3, 104, 52, 0, 416, 412, 1, 0, 0, 0, 416, 414, 1, 0, 0, 0, 416, 417, 1, 0, 0, 0, 417, 436, 1, 0, 0, 0, 418, 419, 5, 52, 0, 0, 419, 422, 3, 106, 53, 0, 420, 421, 5, 98, 0, 0, 421, 423, 5, 82, 0, 0, 422, 420, 1, 0, 0, 0, 422, 423, 1, 0, 0, 0, 423, 424, 1, 0, 0, 0, 424, 425, 5, 59, 0, 0, 425, 426, 3, 106, 53, 0, 426, 436, 1, 0, 0, 0, 427, 428, 5, 52, 0, 0, 428, 429, 3, 106, 53, 0, 429, 430, 5, 59, 0, 0, 430, 433, 3, 106, 53, 0, 431, 432, 5, 11, 0, 0, 432, 434, 3, 104, 52, 0, 433, 431, 1, 0, 0, 0, 433, 434, 1, 0, 0, 0, 434, 436, 1, 0, 0, 0, 435, 406, 1, 0, 0, 0, 435, 418, 1, 0, 0, 0, 435, 427, 1, 0, 0, 0, 436, 63, 1, 0, 0, 0, 437, 438, 5, 59, 0, 0, 438, 439, 3, 106, 53, 0, 439, 65, 1, 0, 0, 0, 440, 441, 5, 79, 0, 0, 441, 442, 3, 84, 42, 0, 442, 67, 1, 0, 0, 0, 443, 444, 6, 34, -1, 0, 444, 446, 3, 126, 63, 0, 445, 447, 5, 27, 0, 0, 446, 445, 1, 0, 0, 0, 446, 447, 1, 0, 0, 0, 447, 449, 1, 0, 0, 0, 448, 450, 3, 76, 38, 0, 449, 448, 1, 0, 0, 0, 449, 450, 1, 0, 0, 0, 450, 456, 1, 0, 0, 0, 451, 452, 5, 126, 0, 0, 452, 453, 3, 68, 34, 0, 453, 454, 5, 144, 0, 0, 454, 456, 1, 0, 0, 0, 455, 443, 1, 0, 0, 0, 455, 451, 1, 0, 0, 0, 456, 471, 1, 0, 0, 0, 457, 458, 10, 3, 0, 0, 458, 459, 3, 72, 36, 0, 459, 460, 3, 68, 34, 4, 460, 470, 1, 0, 0, 0, 461, 463, 10, 4, 0, 0, 462, 464, 3, 70, 35, 0, 463, 462, 1, 0, 0, 0, 463, 464, 1, 0, 0, 0, 464, 465, 1, 0, 0, 0, 465, 466, 5, 45, 0, 0, 466, 467, 3, 68, 34, 0, 467, 468, 3, 74, 37, 0, 468, 470, 1, 0, 0, 0, 469, 457, 1, 0, 0, 0, 469, 461, 1, 0, 0, 0, 470, 473, 1, 0, 0, 0, 471, 469, 1, 0, 0, 0, 471, 472, 1, 0, 0, 0, 472, 69, 1, 0, 0, 0, 473, 471, 1, 0, 0, 0, 474, 476, 7, 2, 0, 0, 475, 474, 1, 0, 0, 0, 475, 476, 1, 0, 0, 0, 476, 477, 1, 0, 0, 0, 477, 484, 5, 42, 0, 0, 478, 480, 5, 42, 0, 0, 479, 481, 7, 2, 0, 0, 480, 479, 1, 0, 0, 0, 480, 481, 1, 0, 0, 0, 481, 484, 1, 0, 0, 0, 482, 484, 7, 2, 0, 0, 483, 475, 1, 0, 0, 0, 483, 478, 1, 0, 0, 0, 483, 482, 1, 0, 0, 0, 484, 518, 1, 0, 0, 0, 485, 487, 7, 3, 0, 0, 486, 485, 1, 0, 0, 0, 486, 487, 1, 0, 0, 0, 487, 488, 1, 0, 0, 0, 488, 490, 7, 4, 0, 0, 489, 491, 5, 63, 0, 0, 490, 489, 1, 0, 0, 0, 490, 491, 1, 0, 0, 0, 491, 500, 1, 0, 0, 0, 492, 494, 7, 4, 0, 0, 493, 495, 5, 63, 0, 0, 494, 493, 1, 0, 0, 0, 494, 495, 1, 0, 0, 0, 495, 497, 1, 0, 0, 0, 496, 498, 7, 3, 0, 0, 497, 496, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 500, 1, 0, 0, 0, 499, 486, 1, 0, 0, 0, 499, 492, 1, 0, 0, 0, 500, 518, 1, 0, 0, 0, 501, 503, 7, 5, 0, 0, 502, 501, 1, 0, 0, 0, 502, 503, 1, 0, 0, 0, 503, 504, 1, 0, 0, 0, 504, 506, 5, 33, 0, 0, 505, 507, 5, 63, 0, 0, 506, 505, 1, 0, 0, 0, 506, 507, 1, 0, 0, 0, 507, 516, 1, 0, 0, 0, 508, 510, 5, 33, 0, 0, 509, 511, 5, 63, 0, 0, 510, 509, 1, 0, 0, 0, 510, 511, 1, 0, 0, 0, 511, 513, 1, 0, 0, 0, 512, 514, 7, 5, 0, 0, 513, 512, 1, 0, 0, 0, 513, 514, 1, 0, 0, 0, 514, 516, 1, 0, 0, 0, 515, 502, 1, 0, 0, 0, 515, 508, 1, 0, 0, 0, 516, 518, 1, 0, 0, 0, 517, 483, 1, 0, 0, 0, 517, 499, 1, 0, 0, 0, 517, 515, 1, 0, 0, 0, 518, 71, 1, 0, 0, 0, 519, 520, 5, 16, 0, 0, 520, 523, 5, 45, 0, 0, 521, 523, 5, 112, 0, 0, 522, 519, 1, 0, 0, 0, 522, 521, 1, 0, 0, 0, 523, 73, 1, 0, 0, 0, 524, 525, 5, 60, 0, 0, 525, 534, 3, 104, 52, 0, 526, 527, 5, 92, 0, 0, 527, 528, 5, 126, 0, 0, 528, 529, 3, 104, 52, 0, 529, 530, 5, 144, 0, 0, 530, 534, 1, 0, 0, 0, 531, 532, 5, 92, 0, 0, 532, 534, 3, 104, 52, 0, 533, 524, 1, 0, 0, 0, 533, 526, 1, 0, 0, 0, 533, 531, 1, 0, 0, 0, 534, 75, 1, 0, 0, 0, 535, 536, 5, 75, 0, 0, 536, 539, 3, 82, 41, 0, 537, 538, 5, 59, 0, 0, 538, 540, 3, 82, 41, 0, 539, 537, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 77, 1, 0, 0, 0, 541, 546, 3, 80, 40, 0, 542, 543, 5, 112, 0, 0, 543, 545, 3, 80, 40, 0, 544, 542, 1, 0, 0, 0, 545, 548, 1, 0, 0, 0, 546, 544, 1, 0, 0, 0, 546, 547, 1, 0, 0, 0, 547, 79, 1, 0, 0, 0, 548, 546, 1, 0, 0, 0, 549, 551, 3, 106, 53, 0, 550, 552, 7, 6, 0, 0, 551, 550, 1, 0, 0, 0, 551, 552, 1, 0, 0, 0, 552, 555, 1, 0, 0, 0, 553, 554, 5, 58, 0, 0, 554, 556, 7, 7, 0, 0, 555, 553, 1, 0, 0, 0, 555, 556, 1, 0, 0, 0, 556, 559, 1, 0, 0, 0, 557, 558, 5, 15, 0, 0, 558, 560, 5, 106, 0, 0, 559, 557, 1, 0, 0, 0, 559, 560, 1, 0, 0, 0, 560, 81, 1, 0, 0, 0, 561, 568, 3, 154, 77, 0, 562, 565, 3, 138, 69, 0, 563, 564, 5, 146, 0, 0, 564, 566, 3, 138, 69, 0, 565, 563, 1, 0, 0, 0, 565, 566, 1, 0, 0, 0, 566, 568, 1, 0, 0, 0, 567, 561, 1, 0, 0, 0, 567, 562, 1, 0, 0, 0, 568, 83, 1, 0, 0, 0, 569, 574, 3, 86, 43, 0, 570, 571, 5, 112, 0, 0, 571, 573, 3, 86, 43, 0, 572, 570, 1, 0, 0, 0, 573, 576, 1, 0, 0, 0, 574, 572, 1, 0, 0, 0, 574, 575, 1, 0, 0, 0, 575, 85, 1, 0, 0, 0, 576, 574, 1, 0, 0, 0, 577, 578, 3, 150, 75, 0, 578, 579, 5, 118, 0, 0, 579, 580, 3, 140, 70, 0, 580, 87, 1, 0, 0, 0, 581, 583, 3, 90, 45, 0, 582, 581, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 585, 1, 0, 0, 0, 584, 586, 3, 92, 46, 0, 585, 584, 1, 0, 0, 0, 585, 586, 1, 0, 0, 0, 586, 588, 1, 0, 0, 0, 587, 589, 3, 94, 47, 0, 588, 587, 1, 0, 0, 0, 588, 589, 1, 0, 0, 0, 589, 89, 1, 0, 0, 0, 590, 591, 5, 65, 0, 0, 591, 592, 5, 11, 0, 0, 592, 593, 3, 104, 52, 0, 593, 91, 1, 0, 0, 0, 594, 595, 5, 62, 0, 0, 595, 596, 5, 11, 0, 0, 596, 597, 3, 78, 39, 0, 597, 93, 1, 0, 0, 0, 598, 599, 7, 8, 0, 0, 599, 600, 3, 96, 48, 0, 600, 95, 1, 0, 0, 0, 601, 608, 3, 98, 49, 0, 602, 603, 5, 9, 0, 0, 603, 604, 3, 98, 49, 0, 604, 605, 5, 2, 0, 0, 605, 606, 3, 98, 49, 0, 606, 608, 1, 0, 0, 0, 607, 601, 1, 0, 0, 0, 607, 602, 1, 0, 0, 0, 608, 97, 1, 0, 0, 0, 609, 610, 5, 18, 0, 0, 610, 622, 5, 73, 0, 0, 611, 612, 5, 90, 0, 0, 612, 622, 5, 66, 0, 0, 613, 614, 5, 90, 0, 0, 614, 622, 5, 30, 0, 0, 615, 616, 3, 138, 69, 0, 616, 617, 5, 66, 0, 0, 617, 622, 1, 0, 0, 0, 618, 619, 3, 138, 69, 0, 619, 620, 5, 30, 0, 0, 620, 622, 1, 0, 0, 0, 621, 609, 1, 0, 0, 0, 621, 611, 1, 0, 0, 0, 621, 613, 1, 0, 0, 0, 621, 615, 1, 0, 0, 0, 621, 618, 1, 0, 0, 0, 622, 99, 1, 0, 0, 0, 623, 624, 3, 106, 53, 0, 624, 625, 5, 0, 0, 1, 625, 101, 1, 0, 0, 0, 626, 674, 3, 150, 75, 0, 627, 628, 3, 150, 75, 0, 628, 629, 5, 126, 0, 0, 629, 630, 3, 150, 75, 0, 630, 637, 3, 102, 51, 0, 631, 632, 5, 112, 0, 0, 632, 633, 3, 150, 75, 0, 633, 634, 3, 102, 51, 0, 634, 636, 1, 0, 0, 0, 635, 631, 1, 0, 0, 0, 636, 639, 1, 0, 0, 0, 637, 635, 1, 0, 0, 0, 637, 638, 1, 0, 0, 0, 638, 640, 1, 0, 0, 0, 639, 637, 1, 0, 0, 0, 640, 641, 5, 144, 0, 0, 641, 674, 1, 0, 0, 0, 642, 643, 3, 150, 75, 0, 643, 644, 5, 126, 0, 0, 644, 649, 3, 152, 76, 0, 645, 646, 5, 112, 0, 0, 646, 648, 3, 152, 76, 0, 647, 645, 1, 0, 0, 0, 648, 651, 1, 0, 0, 0, 649, 647, 1, 0, 0, 0, 649, 650, 1, 0, 0, 0, 650, 652, 1, 0, 0, 0, 651, 649, 1, 0, 0, 0, 652, 653, 5, 144, 0, 0, 653, 674, 1, 0, 0, 0, 654, 655, 3, 150, 75, 0, 655, 656, 5, 126, 0, 0, 656, 661, 3, 102, 51, 0, 657, 658, 5, 112, 0, 0, 658, 660, 3, 102, 51, 0, 659, 657, 1, 0, 0, 0, 660, 663, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 664, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 664, 665, 5, 144, 0, 0, 665, 674, 1, 0, 0, 0, 666, 667, 3, 150, 75, 0, 667, 669, 5, 126, 0, 0, 668, 670, 3, 104, 52, 0, 669, 668, 1, 0, 0, 0, 669, 670, 1, 0, 0, 0, 670, 671, 1, 0, 0, 0, 671, 672, 5, 144, 0, 0, 672, 674, 1, 0, 0, 0, 673, 626, 1, 0, 0, 0, 673, 627, 1, 0, 0, 0, 673, 642, 1, 0, 0, 0, 673, 654, 1, 0, 0, 0, 673, 666, 1, 0, 0, 0, 674, 103, 1, 0, 0, 0, 675, 680, 3, 106, 53, 0, 676, 677, 5, 112, 0, 0, 677, 679, 3, 106, 53, 0, 678, 676, 1, 0, 0, 0, 679, 682, 1, 0, 0, 0, 680, 678, 1, 0, 0, 0, 680, 681, 1, 0, 0, 0, 681, 105, 1, 0, 0, 0, 682, 680, 1, 0, 0, 0, 683, 684, 6, 53, -1, 0, 684, 686, 5, 12, 0, 0, 685, 687, 3, 106, 53, 0, 686, 685, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 693, 1, 0, 0, 0, 688, 689, 5, 94, 0, 0, 689, 690, 3, 106, 53, 0, 690, 691, 5, 81, 0, 0, 691, 692, 3, 106, 53, 0, 692, 694, 1, 0, 0, 0, 693, 688, 1, 0, 0, 0, 694, 695, 1, 0, 0, 0, 695, 693, 1, 0, 0, 0, 695, 696, 1, 0, 0, 0, 696, 699, 1, 0, 0, 0, 697, 698, 5, 24, 0, 0, 698, 700, 3, 106, 53, 0, 699, 697, 1, 0, 0, 0, 699, 700, 1, 0, 0, 0, 700, 701, 1, 0, 0, 0, 701, 702, 5, 25, 0, 0, 702, 833, 1, 0, 0, 0, 703, 704, 5, 13, 0, 0, 704, 705, 5, 126, 0, 0, 705, 706, 3, 106, 53, 0, 706, 707, 5, 6, 0, 0, 707, 708, 3, 102, 51, 0, 708, 709, 5, 144, 0, 0, 709, 833, 1, 0, 0, 0, 710, 711, 5, 19, 0, 0, 711, 833, 5, 106, 0, 0, 712, 713, 5, 43, 0, 0, 713, 714, 3, 106, 53, 0, 714, 715, 3, 142, 71, 0, 715, 833, 1, 0, 0, 0, 716, 717, 5, 80, 0, 0, 717, 718, 5, 126, 0, 0, 718, 719, 3, 106, 53, 0, 719, 720, 5, 32, 0, 0, 720, 723, 3, 106, 53, 0, 721, 722, 5, 31, 0, 0, 722, 724, 3, 106, 53, 0, 723, 721, 1, 0, 0, 0, 723, 724, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 726, 5, 144, 0, 0, 726, 833, 1, 0, 0, 0, 727, 728, 5, 83, 0, 0, 728, 833, 5, 106, 0, 0, 729, 730, 5, 88, 0, 0, 730, 731, 5, 126, 0, 0, 731, 732, 7, 9, 0, 0, 732, 733, 3, 156, 78, 0, 733, 734, 5, 32, 0, 0, 734, 735, 3, 106, 53, 0, 735, 736, 5, 144, 0, 0, 736, 833, 1, 0, 0, 0, 737, 738, 3, 150, 75, 0, 738, 740, 5, 126, 0, 0, 739, 741, 3, 104, 52, 0, 740, 739, 1, 0, 0, 0, 740, 741, 1, 0, 0, 0, 741, 742, 1, 0, 0, 0, 742, 743, 5, 144, 0, 0, 743, 752, 1, 0, 0, 0, 744, 746, 5, 126, 0, 0, 745, 747, 5, 23, 0, 0, 746, 745, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 749, 1, 0, 0, 0, 748, 750, 3, 108, 54, 0, 749, 748, 1, 0, 0, 0, 749, 750, 1, 0, 0, 0, 750, 751, 1, 0, 0, 0, 751, 753, 5, 144, 0, 0, 752, 744, 1, 0, 0, 0, 752, 753, 1, 0, 0, 0, 753, 754, 1, 0, 0, 0, 754, 755, 5, 64, 0, 0, 755, 756, 5, 126, 0, 0, 756, 757, 3, 88, 44, 0, 757, 758, 5, 144, 0, 0, 758, 833, 1, 0, 0, 0, 759, 760, 3, 150, 75, 0, 760, 762, 5, 126, 0, 0, 761, 763, 3, 104, 52, 0, 762, 761, 1, 0, 0, 0, 762, 763, 1, 0, 0, 0, 763, 764, 1, 0, 0, 0, 764, 765, 5, 144, 0, 0, 765, 774, 1, 0, 0, 0, 766, 768, 5, 126, 0, 0, 767, 769, 5, 23, 0, 0, 768, 767, 1, 0, 0, 0, 768, 769, 1, 0, 0, 0, 769, 771, 1, 0, 0, 0, 770, 772, 3, 108, 54, 0, 771, 770, 1, 0, 0, 0, 771, 772, 1, 0, 0, 0, 772, 773, 1, 0, 0, 0, 773, 775, 5, 144, 0, 0, 774, 766, 1, 0, 0, 0, 774, 775, 1, 0, 0, 0, 775, 776, 1, 0, 0, 0, 776, 777, 5, 64, 0, 0, 777, 778, 3, 150, 75, 0, 778, 833, 1, 0, 0, 0, 779, 785, 3, 150, 75, 0, 780, 782, 5, 126, 0, 0, 781, 783, 3, 104, 52, 0, 782, 781, 1, 0, 0, 0, 782, 783, 1, 0, 0, 0, 783, 784, 1, 0, 0, 0, 784, 786, 5, 144, 0, 0, 785, 780, 1, 0, 0, 0, 785, 786, 1, 0, 0, 0, 786, 787, 1, 0, 0, 0, 787, 789, 5, 126, 0, 0, 788, 790, 5, 23, 0, 0, 789, 788, 1, 0, 0, 0, 789, 790, 1, 0, 0, 0, 790, 792, 1, 0, 0, 0, 791, 793, 3, 108, 54, 0, 792, 791, 1, 0, 0, 0, 792, 793, 1, 0, 0, 0, 793, 794, 1, 0, 0, 0, 794, 795, 5, 144, 0, 0, 795, 833, 1, 0, 0, 0, 796, 833, 3, 114, 57, 0, 797, 833, 3, 158, 79, 0, 798, 833, 3, 140, 70, 0, 799, 800, 5, 114, 0, 0, 800, 833, 3, 106, 53, 19, 801, 802, 5, 56, 0, 0, 802, 833, 3, 106, 53, 13, 803, 804, 3, 130, 65, 0, 804, 805, 5, 116, 0, 0, 805, 807, 1, 0, 0, 0, 806, 803, 1, 0, 0, 0, 806, 807, 1, 0, 0, 0, 807, 808, 1, 0, 0, 0, 808, 833, 5, 108, 0, 0, 809, 810, 5, 126, 0, 0, 810, 811, 3, 34, 17, 0, 811, 812, 5, 144, 0, 0, 812, 833, 1, 0, 0, 0, 813, 814, 5, 126, 0, 0, 814, 815, 3, 106, 53, 0, 815, 816, 5, 144, 0, 0, 816, 833, 1, 0, 0, 0, 817, 818, 5, 126, 0, 0, 818, 819, 3, 104, 52, 0, 819, 820, 5, 144, 0, 0, 820, 833, 1, 0, 0, 0, 821, 823, 5, 125, 0, 0, 822, 824, 3, 104, 52, 0, 823, 822, 1, 0, 0, 0, 823, 824, 1, 0, 0, 0, 824, 825, 1, 0, 0, 0, 825, 833, 5, 143, 0, 0, 826, 828, 5, 124, 0, 0, 827, 829, 3, 30, 15, 0, 828, 827, 1, 0, 0, 0, 828, 829, 1, 0, 0, 0, 829, 830, 1, 0, 0, 0, 830, 833, 5, 142, 0, 0, 831, 833, 3, 122, 61, 0, 832, 683, 1, 0, 0, 0, 832, 703, 1, 0, 0, 0, 832, 710, 1, 0, 0, 0, 832, 712, 1, 0, 0, 0, 832, 716, 1, 0, 0, 0, 832, 727, 1, 0, 0, 0, 832, 729, 1, 0, 0, 0, 832, 737, 1, 0, 0, 0, 832, 759, 1, 0, 0, 0, 832, 779, 1, 0, 0, 0, 832, 796, 1, 0, 0, 0, 832, 797, 1, 0, 0, 0, 832, 798, 1, 0, 0, 0, 832, 799, 1, 0, 0, 0, 832, 801, 1, 0, 0, 0, 832, 806, 1, 0, 0, 0, 832, 809, 1, 0, 0, 0, 832, 813, 1, 0, 0, 0, 832, 817, 1, 0, 0, 0, 832, 821, 1, 0, 0, 0, 832, 826, 1, 0, 0, 0, 832, 831, 1, 0, 0, 0, 833, 927, 1, 0, 0, 0, 834, 838, 10, 18, 0, 0, 835, 839, 5, 108, 0, 0, 836, 839, 5, 146, 0, 0, 837, 839, 5, 133, 0, 0, 838, 835, 1, 0, 0, 0, 838, 836, 1, 0, 0, 0, 838, 837, 1, 0, 0, 0, 839, 840, 1, 0, 0, 0, 840, 926, 3, 106, 53, 19, 841, 845, 10, 17, 0, 0, 842, 846, 5, 134, 0, 0, 843, 846, 5, 114, 0, 0, 844, 846, 5, 113, 0, 0, 845, 842, 1, 0, 0, 0, 845, 843, 1, 0, 0, 0, 845, 844, 1, 0, 0, 0, 846, 847, 1, 0, 0, 0, 847, 926, 3, 106, 53, 18, 848, 873, 10, 16, 0, 0, 849, 874, 5, 117, 0, 0, 850, 874, 5, 118, 0, 0, 851, 874, 5, 129, 0, 0, 852, 874, 5, 127, 0, 0, 853, 874, 5, 128, 0, 0, 854, 874, 5, 119, 0, 0, 855, 874, 5, 120, 0, 0, 856, 858, 5, 56, 0, 0, 857, 856, 1, 0, 0, 0, 857, 858, 1, 0, 0, 0, 858, 859, 1, 0, 0, 0, 859, 861, 5, 40, 0, 0, 860, 862, 5, 14, 0, 0, 861, 860, 1, 0, 0, 0, 861, 862, 1, 0, 0, 0, 862, 874, 1, 0, 0, 0, 863, 865, 5, 56, 0, 0, 864, 863, 1, 0, 0, 0, 864, 865, 1, 0, 0, 0, 865, 866, 1, 0, 0, 0, 866, 874, 7, 10, 0, 0, 867, 874, 5, 140, 0, 0, 868, 874, 5, 141, 0, 0, 869, 874, 5, 131, 0, 0, 870, 874, 5, 122, 0, 0, 871, 874, 5, 123, 0, 0, 872, 874, 5, 130, 0, 0, 873, 849, 1, 0, 0, 0, 873, 850, 1, 0, 0, 0, 873, 851, 1, 0, 0, 0, 873, 852, 1, 0, 0, 0, 873, 853, 1, 0, 0, 0, 873, 854, 1, 0, 0, 0, 873, 855, 1, 0, 0, 0, 873, 857, 1, 0, 0, 0, 873, 864, 1, 0, 0, 0, 873, 867, 1, 0, 0, 0, 873, 868, 1, 0, 0, 0, 873, 869, 1, 0, 0, 0, 873, 870, 1, 0, 0, 0, 873, 871, 1, 0, 0, 0, 873, 872, 1, 0, 0, 0, 874, 875, 1, 0, 0, 0, 875, 926, 3, 106, 53, 17, 876, 877, 10, 14, 0, 0, 877, 878, 5, 132, 0, 0, 878, 926, 3, 106, 53, 15, 879, 880, 10, 12, 0, 0, 880, 881, 5, 2, 0, 0, 881, 926, 3, 106, 53, 13, 882, 883, 10, 11, 0, 0, 883, 884, 5, 61, 0, 0, 884, 926, 3, 106, 53, 12, 885, 887, 10, 10, 0, 0, 886, 888, 5, 56, 0, 0, 887, 886, 1, 0, 0, 0, 887, 888, 1, 0, 0, 0, 888, 889, 1, 0, 0, 0, 889, 890, 5, 9, 0, 0, 890, 891, 3, 106, 53, 0, 891, 892, 5, 2, 0, 0, 892, 893, 3, 106, 53, 11, 893, 926, 1, 0, 0, 0, 894, 895, 10, 9, 0, 0, 895, 896, 5, 135, 0, 0, 896, 897, 3, 106, 53, 0, 897, 898, 5, 111, 0, 0, 898, 899, 3, 106, 53, 9, 899, 926, 1, 0, 0, 0, 900, 901, 10, 22, 0, 0, 901, 902, 5, 125, 0, 0, 902, 903, 3, 106, 53, 0, 903, 904, 5, 143, 0, 0, 904, 926, 1, 0, 0, 0, 905, 906, 10, 21, 0, 0, 906, 907, 5, 116, 0, 0, 907, 926, 5, 104, 0, 0, 908, 909, 10, 20, 0, 0, 909, 910, 5, 116, 0, 0, 910, 926, 3, 150, 75, 0, 911, 912, 10, 15, 0, 0, 912, 914, 5, 44, 0, 0, 913, 915, 5, 56, 0, 0, 914, 913, 1, 0, 0, 0, 914, 915, 1, 0, 0, 0, 915, 916, 1, 0, 0, 0, 916, 926, 5, 57, 0, 0, 917, 923, 10, 8, 0, 0, 918, 924, 3, 148, 74, 0, 919, 920, 5, 6, 0, 0, 920, 924, 3, 150, 75, 0, 921, 922, 5, 6, 0, 0, 922, 924, 5, 106, 0, 0, 923, 918, 1, 0, 0, 0, 923, 919, 1, 0, 0, 0, 923, 921, 1, 0, 0, 0, 924, 926, 1, 0, 0, 0, 925, 834, 1, 0, 0, 0, 925, 841, 1, 0, 0, 0, 925, 848, 1, 0, 0, 0, 925, 876, 1, 0, 0, 0, 925, 879, 1, 0, 0, 0, 925, 882, 1, 0, 0, 0, 925, 885, 1, 0, 0, 0, 925, 894, 1, 0, 0, 0, 925, 900, 1, 0, 0, 0, 925, 905, 1, 0, 0, 0, 925, 908, 1, 0, 0, 0, 925, 911, 1, 0, 0, 0, 925, 917, 1, 0, 0, 0, 926, 929, 1, 0, 0, 0, 927, 925, 1, 0, 0, 0, 927, 928, 1, 0, 0, 0, 928, 107, 1, 0, 0, 0, 929, 927, 1, 0, 0, 0, 930, 935, 3, 110, 55, 0, 931, 932, 5, 112, 0, 0, 932, 934, 3, 110, 55, 0, 933, 931, 1, 0, 0, 0, 934, 937, 1, 0, 0, 0, 935, 933, 1, 0, 0, 0, 935, 936, 1, 0, 0, 0, 936, 109, 1, 0, 0, 0, 937, 935, 1, 0, 0, 0, 938, 941, 3, 112, 56, 0, 939, 941, 3, 106, 53, 0, 940, 938, 1, 0, 0, 0, 940, 939, 1, 0, 0, 0, 941, 111, 1, 0, 0, 0, 942, 943, 5, 126, 0, 0, 943, 948, 3, 150, 75, 0, 944, 945, 5, 112, 0, 0, 945, 947, 3, 150, 75, 0, 946, 944, 1, 0, 0, 0, 947, 950, 1, 0, 0, 0, 948, 946, 1, 0, 0, 0, 948, 949, 1, 0, 0, 0, 949, 951, 1, 0, 0, 0, 950, 948, 1, 0, 0, 0, 951, 952, 5, 144, 0, 0, 952, 962, 1, 0, 0, 0, 953, 958, 3, 150, 75, 0, 954, 955, 5, 112, 0, 0, 955, 957, 3, 150, 75, 0, 956, 954, 1, 0, 0, 0, 957, 960, 1, 0, 0, 0, 958, 956, 1, 0, 0, 0, 958, 959, 1, 0, 0, 0, 959, 962, 1, 0, 0, 0, 960, 958, 1, 0, 0, 0, 961, 942, 1, 0, 0, 0, 961, 953, 1, 0, 0, 0, 962, 963, 1, 0, 0, 0, 963, 964, 5, 107, 0, 0, 964, 965, 3, 106, 53, 0, 965, 113, 1, 0, 0, 0, 966, 967, 5, 128, 0, 0, 967, 971, 3, 150, 75, 0, 968, 970, 3, 116, 58, 0, 969, 968, 1, 0, 0, 0, 970, 973, 1, 0, 0, 0, 971, 969, 1, 0, 0, 0, 971, 972, 1, 0, 0, 0, 972, 974, 1, 0, 0, 0, 973, 971, 1, 0, 0, 0, 974, 975, 5, 146, 0, 0, 975, 976, 5, 120, 0, 0, 976, 995, 1, 0, 0, 0, 977, 978, 5, 128, 0, 0, 978, 982, 3, 150, 75, 0, 979, 981, 3, 116, 58, 0, 980, 979, 1, 0, 0, 0, 981, 984, 1, 0, 0, 0, 982, 980, 1, 0, 0, 0, 982, 983, 1, 0, 0, 0, 983, 985, 1, 0, 0, 0, 984, 982, 1, 0, 0, 0, 985, 987, 5, 120, 0, 0, 986, 988, 3, 114, 57, 0, 987, 986, 1, 0, 0, 0, 987, 988, 1, 0, 0, 0, 988, 989, 1, 0, 0, 0, 989, 990, 5, 128, 0, 0, 990, 991, 5, 146, 0, 0, 991, 992, 3, 150, 75, 0, 992, 993, 5, 120, 0, 0, 993, 995, 1, 0, 0, 0, 994, 966, 1, 0, 0, 0, 994, 977, 1, 0, 0, 0, 995, 115, 1, 0, 0, 0, 996, 997, 3, 150, 75, 0, 997, 998, 5, 118, 0, 0, 998, 999, 3, 156, 78, 0, 999, 1008, 1, 0, 0, 0, 1000, 1001, 3, 150, 75, 0, 1001, 1002, 5, 118, 0, 0, 1002, 1003, 5, 124, 0, 0, 1003, 1004, 3, 106, 53, 0, 1004, 1005, 5, 142, 0, 0, 1005, 1008, 1, 0, 0, 0, 1006, 1008, 3, 150, 75, 0, 1007, 996, 1, 0, 0, 0, 1007, 1000, 1, 0, 0, 0, 1007, 1006, 1, 0, 0, 0, 1008, 117, 1, 0, 0, 0, 1009, 1014, 3, 120, 60, 0, 1010, 1011, 5, 112, 0, 0, 1011, 1013, 3, 120, 60, 0, 1012, 1010, 1, 0, 0, 0, 1013, 1016, 1, 0, 0, 0, 1014, 1012, 1, 0, 0, 0, 1014, 1015, 1, 0, 0, 0, 1015, 119, 1, 0, 0, 0, 1016, 1014, 1, 0, 0, 0, 1017, 1018, 3, 150, 75, 0, 1018, 1019, 5, 6, 0, 0, 1019, 1020, 5, 126, 0, 0, 1020, 1021, 3, 34, 17, 0, 1021, 1022, 5, 144, 0, 0, 1022, 1028, 1, 0, 0, 0, 1023, 1024, 3, 106, 53, 0, 1024, 1025, 5, 6, 0, 0, 1025, 1026, 3, 150, 75, 0, 1026, 1028, 1, 0, 0, 0, 1027, 1017, 1, 0, 0, 0, 1027, 1023, 1, 0, 0, 0, 1028, 121, 1, 0, 0, 0, 1029, 1037, 3, 154, 77, 0, 1030, 1031, 3, 130, 65, 0, 1031, 1032, 5, 116, 0, 0, 1032, 1034, 1, 0, 0, 0, 1033, 1030, 1, 0, 0, 0, 1033, 1034, 1, 0, 0, 0, 1034, 1035, 1, 0, 0, 0, 1035, 1037, 3, 124, 62, 0, 1036, 1029, 1, 0, 0, 0, 1036, 1033, 1, 0, 0, 0, 1037, 123, 1, 0, 0, 0, 1038, 1043, 3, 150, 75, 0, 1039, 1040, 5, 116, 0, 0, 1040, 1042, 3, 150, 75, 0, 1041, 1039, 1, 0, 0, 0, 1042, 1045, 1, 0, 0, 0, 1043, 1041, 1, 0, 0, 0, 1043, 1044, 1, 0, 0, 0, 1044, 125, 1, 0, 0, 0, 1045, 1043, 1, 0, 0, 0, 1046, 1047, 6, 63, -1, 0, 1047, 1056, 3, 130, 65, 0, 1048, 1056, 3, 128, 64, 0, 1049, 1050, 5, 126, 0, 0, 1050, 1051, 3, 34, 17, 0, 1051, 1052, 5, 144, 0, 0, 1052, 1056, 1, 0, 0, 0, 1053, 1056, 3, 114, 57, 0, 1054, 1056, 3, 154, 77, 0, 1055, 1046, 1, 0, 0, 0, 1055, 1048, 1, 0, 0, 0, 1055, 1049, 1, 0, 0, 0, 1055, 1053, 1, 0, 0, 0, 1055, 1054, 1, 0, 0, 0, 1056, 1065, 1, 0, 0, 0, 1057, 1061, 10, 3, 0, 0, 1058, 1062, 3, 148, 74, 0, 1059, 1060, 5, 6, 0, 0, 1060, 1062, 3, 150, 75, 0, 1061, 1058, 1, 0, 0, 0, 1061, 1059, 1, 0, 0, 0, 1062, 1064, 1, 0, 0, 0, 1063, 1057, 1, 0, 0, 0, 1064, 1067, 1, 0, 0, 0, 1065, 1063, 1, 0, 0, 0, 1065, 1066, 1, 0, 0, 0, 1066, 127, 1, 0, 0, 0, 1067, 1065, 1, 0, 0, 0, 1068, 1069, 3, 150, 75, 0, 1069, 1071, 5, 126, 0, 0, 1070, 1072, 3, 132, 66, 0, 1071, 1070, 1, 0, 0, 0, 1071, 1072, 1, 0, 0, 0, 1072, 1073, 1, 0, 0, 0, 1073, 1074, 5, 144, 0, 0, 1074, 129, 1, 0, 0, 0, 1075, 1076, 3, 134, 67, 0, 1076, 1077, 5, 116, 0, 0, 1077, 1079, 1, 0, 0, 0, 1078, 1075, 1, 0, 0, 0, 1078, 1079, 1, 0, 0, 0, 1079, 1080, 1, 0, 0, 0, 1080, 1081, 3, 150, 75, 0, 1081, 131, 1, 0, 0, 0, 1082, 1087, 3, 106, 53, 0, 1083, 1084, 5, 112, 0, 0, 1084, 1086, 3, 106, 53, 0, 1085, 1083, 1, 0, 0, 0, 1086, 1089, 1, 0, 0, 0, 1087, 1085, 1, 0, 0, 0, 1087, 1088, 1, 0, 0, 0, 1088, 133, 1, 0, 0, 0, 1089, 1087, 1, 0, 0, 0, 1090, 1091, 3, 150, 75, 0, 1091, 135, 1, 0, 0, 0, 1092, 1101, 5, 102, 0, 0, 1093, 1094, 5, 116, 0, 0, 1094, 1101, 7, 11, 0, 0, 1095, 1096, 5, 104, 0, 0, 1096, 1098, 5, 116, 0, 0, 1097, 1099, 7, 11, 0, 0, 1098, 1097, 1, 0, 0, 0, 1098, 1099, 1, 0, 0, 0, 1099, 1101, 1, 0, 0, 0, 1100, 1092, 1, 0, 0, 0, 1100, 1093, 1, 0, 0, 0, 1100, 1095, 1, 0, 0, 0, 1101, 137, 1, 0, 0, 0, 1102, 1104, 7, 12, 0, 0, 1103, 1102, 1, 0, 0, 0, 1103, 1104, 1, 0, 0, 0, 1104, 1111, 1, 0, 0, 0, 1105, 1112, 3, 136, 68, 0, 1106, 1112, 5, 103, 0, 0, 1107, 1112, 5, 104, 0, 0, 1108, 1112, 5, 105, 0, 0, 1109, 1112, 5, 41, 0, 0, 1110, 1112, 5, 55, 0, 0, 1111, 1105, 1, 0, 0, 0, 1111, 1106, 1, 0, 0, 0, 1111, 1107, 1, 0, 0, 0, 1111, 1108, 1, 0, 0, 0, 1111, 1109, 1, 0, 0, 0, 1111, 1110, 1, 0, 0, 0, 1112, 139, 1, 0, 0, 0, 1113, 1117, 3, 138, 69, 0, 1114, 1117, 5, 106, 0, 0, 1115, 1117, 5, 57, 0, 0, 1116, 1113, 1, 0, 0, 0, 1116, 1114, 1, 0, 0, 0, 1116, 1115, 1, 0, 0, 0, 1117, 141, 1, 0, 0, 0, 1118, 1119, 7, 13, 0, 0, 1119, 143, 1, 0, 0, 0, 1120, 1121, 7, 14, 0, 0, 1121, 145, 1, 0, 0, 0, 1122, 1123, 7, 15, 0, 0, 1123, 147, 1, 0, 0, 0, 1124, 1127, 5, 101, 0, 0, 1125, 1127, 3, 146, 73, 0, 1126, 1124, 1, 0, 0, 0, 1126, 1125, 1, 0, 0, 0, 1127, 149, 1, 0, 0, 0, 1128, 1132, 5, 101, 0, 0, 1129, 1132, 3, 142, 71, 0, 1130, 1132, 3, 144, 72, 0, 1131, 1128, 1, 0, 0, 0, 1131, 1129, 1, 0, 0, 0, 1131, 1130, 1, 0, 0, 0, 1132, 151, 1, 0, 0, 0, 1133, 1134, 3, 156, 78, 0, 1134, 1135, 5, 118, 0, 0, 1135, 1136, 3, 138, 69, 0, 1136, 153, 1, 0, 0, 0, 1137, 1138, 5, 124, 0, 0, 1138, 1139, 3, 150, 75, 0, 1139, 1140, 5, 142, 0, 0, 1140, 155, 1, 0, 0, 0, 1141, 1144, 5, 106, 0, 0, 1142, 1144, 3, 158, 79, 0, 1143, 1141, 1, 0, 0, 0, 1143, 1142, 1, 0, 0, 0, 1144, 157, 1, 0, 0, 0, 1145, 1149, 5, 137, 0, 0, 1146, 1148, 3, 160, 80, 0, 1147, 1146, 1, 0, 0, 0, 1148, 1151, 1, 0, 0, 0, 1149, 1147, 1, 0, 0, 0, 1149, 1150, 1, 0, 0, 0, 1150, 1152, 1, 0, 0, 0, 1151, 1149, 1, 0, 0, 0, 1152, 1153, 5, 139, 0, 0, 1153, 159, 1, 0, 0, 0, 1154, 1155, 5, 152, 0, 0, 1155, 1156, 3, 106, 53, 0, 1156, 1157, 5, 142, 0, 0, 1157, 1160, 1, 0, 0, 0, 1158, 1160, 5, 151, 0, 0, 1159, 1154, 1, 0, 0, 0, 1159, 1158, 1, 0, 0, 0, 1160, 161, 1, 0, 0, 0, 1161, 1165, 5, 138, 0, 0, 1162, 1164, 3, 164, 82, 0, 1163, 1162, 1, 0, 0, 0, 1164, 1167, 1, 0, 0, 0, 1165, 1163, 1, 0, 0, 0, 1165, 1166, 1, 0, 0, 0, 1166, 1168, 1, 0, 0, 0, 1167, 1165, 1, 0, 0, 0, 1168, 1169, 5, 0, 0, 1, 1169, 163, 1, 0, 0, 0, 1170, 1171, 5, 154, 0, 0, 1171, 1172, 3, 106, 53, 0, 1172, 1173, 5, 142, 0, 0, 1173, 1176, 1, 0, 0, 0, 1174, 1176, 5, 153, 0, 0, 1175, 1170, 1, 0, 0, 0, 1175, 1174, 1, 0, 0, 0, 1176, 165, 1, 0, 0, 0, 141, 169, 176, 185, 200, 212, 224, 240, 251, 265, 271, 281, 290, 293, 297, 300, 304, 307, 310, 313, 316, 320, 324, 327, 330, 333, 337, 340, 349, 355, 376, 393, 410, 416, 422, 433, 435, 446, 449, 455, 463, 469, 471, 475, 480, 483, 486, 490, 494, 497, 499, 502, 506, 510, 513, 515, 517, 522, 533, 539, 546, 551, 555, 559, 565, 567, 574, 582, 585, 588, 607, 621, 637, 649, 661, 669, 673, 680, 686, 695, 699, 723, 740, 746, 749, 752, 762, 768, 771, 774, 782, 785, 789, 792, 806, 823, 828, 832, 838, 845, 857, 861, 864, 873, 887, 914, 923, 925, 927, 935, 940, 948, 958, 961, 971, 982, 987, 994, 1007, 1014, 1027, 1033, 1036, 1043, 1055, 1061, 1065, 1071, 1078, 1087, 1098, 1100, 1103, 1111, 1116, 1126, 1131, 1143, 1149, 1159, 1165, 1175] \ No newline at end of file diff --git a/posthog/hogql/grammar/HogQLParser.py b/posthog/hogql/grammar/HogQLParser.py index 50e81765f514d6..9c0c50c0835da4 100644 --- a/posthog/hogql/grammar/HogQLParser.py +++ b/posthog/hogql/grammar/HogQLParser.py @@ -10,7 +10,7 @@ def serializedATN(): return [ - 4,1,154,1158,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6, + 4,1,154,1178,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6, 7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7, 13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,19,2, 20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,2,26,7, @@ -72,74 +72,76 @@ def serializedATN(): 53,700,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1, 53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,724, 8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,3,53,741,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,3,53,753,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,3,53,763,8,53,1,53,3,53,766,8,53,1,53,1,53,3,53,770,8,53,1, - 53,3,53,773,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1, - 53,1,53,1,53,3,53,787,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1, - 53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,804,8,53,1,53,1,53,1, - 53,3,53,809,8,53,1,53,1,53,3,53,813,8,53,1,53,1,53,1,53,1,53,3,53, - 819,8,53,1,53,1,53,1,53,1,53,1,53,3,53,826,8,53,1,53,1,53,1,53,1, - 53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,838,8,53,1,53,1,53,3,53,842, - 8,53,1,53,3,53,845,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53, - 854,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,3,53,868,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, + 1,53,1,53,1,53,3,53,741,8,53,1,53,1,53,1,53,1,53,3,53,747,8,53,1, + 53,3,53,750,8,53,1,53,3,53,753,8,53,1,53,1,53,1,53,1,53,1,53,1,53, + 1,53,1,53,3,53,763,8,53,1,53,1,53,1,53,1,53,3,53,769,8,53,1,53,3, + 53,772,8,53,1,53,3,53,775,8,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53, + 783,8,53,1,53,3,53,786,8,53,1,53,1,53,3,53,790,8,53,1,53,3,53,793, + 8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, + 3,53,807,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, + 1,53,1,53,1,53,1,53,1,53,3,53,824,8,53,1,53,1,53,1,53,3,53,829,8, + 53,1,53,1,53,3,53,833,8,53,1,53,1,53,1,53,1,53,3,53,839,8,53,1,53, + 1,53,1,53,1,53,1,53,3,53,846,8,53,1,53,1,53,1,53,1,53,1,53,1,53, + 1,53,1,53,1,53,1,53,3,53,858,8,53,1,53,1,53,3,53,862,8,53,1,53,3, + 53,865,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,874,8,53,1,53, + 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,888, + 8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, 1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 1,53,1,53,1,53,3,53,895,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53, - 3,53,904,8,53,5,53,906,8,53,10,53,12,53,909,9,53,1,54,1,54,1,54, - 5,54,914,8,54,10,54,12,54,917,9,54,1,55,1,55,3,55,921,8,55,1,56, - 1,56,1,56,1,56,5,56,927,8,56,10,56,12,56,930,9,56,1,56,1,56,1,56, - 1,56,1,56,5,56,937,8,56,10,56,12,56,940,9,56,3,56,942,8,56,1,56, - 1,56,1,56,1,57,1,57,1,57,5,57,950,8,57,10,57,12,57,953,9,57,1,57, - 1,57,1,57,1,57,1,57,1,57,5,57,961,8,57,10,57,12,57,964,9,57,1,57, - 1,57,3,57,968,8,57,1,57,1,57,1,57,1,57,1,57,3,57,975,8,57,1,58,1, - 58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,1,58,3,58,988,8,58,1, - 59,1,59,1,59,5,59,993,8,59,10,59,12,59,996,9,59,1,60,1,60,1,60,1, - 60,1,60,1,60,1,60,1,60,1,60,1,60,3,60,1008,8,60,1,61,1,61,1,61,1, - 61,3,61,1014,8,61,1,61,3,61,1017,8,61,1,62,1,62,1,62,5,62,1022,8, - 62,10,62,12,62,1025,9,62,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63, - 1,63,3,63,1036,8,63,1,63,1,63,1,63,1,63,3,63,1042,8,63,5,63,1044, - 8,63,10,63,12,63,1047,9,63,1,64,1,64,1,64,3,64,1052,8,64,1,64,1, - 64,1,65,1,65,1,65,3,65,1059,8,65,1,65,1,65,1,66,1,66,1,66,5,66,1066, - 8,66,10,66,12,66,1069,9,66,1,67,1,67,1,68,1,68,1,68,1,68,1,68,1, - 68,3,68,1079,8,68,3,68,1081,8,68,1,69,3,69,1084,8,69,1,69,1,69,1, - 69,1,69,1,69,1,69,3,69,1092,8,69,1,70,1,70,1,70,3,70,1097,8,70,1, - 71,1,71,1,72,1,72,1,73,1,73,1,74,1,74,3,74,1107,8,74,1,75,1,75,1, - 75,3,75,1112,8,75,1,76,1,76,1,76,1,76,1,77,1,77,1,77,1,77,1,78,1, - 78,3,78,1124,8,78,1,79,1,79,5,79,1128,8,79,10,79,12,79,1131,9,79, - 1,79,1,79,1,80,1,80,1,80,1,80,1,80,3,80,1140,8,80,1,81,1,81,5,81, - 1144,8,81,10,81,12,81,1147,9,81,1,81,1,81,1,82,1,82,1,82,1,82,1, - 82,3,82,1156,8,82,1,82,0,3,68,106,126,83,0,2,4,6,8,10,12,14,16,18, - 20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62, - 64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104, - 106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136, - 138,140,142,144,146,148,150,152,154,156,158,160,162,164,0,16,2,0, - 17,17,72,72,2,0,42,42,49,49,3,0,1,1,4,4,8,8,4,0,1,1,3,4,8,8,78,78, - 2,0,49,49,71,71,2,0,1,1,4,4,2,0,7,7,21,22,2,0,28,28,47,47,2,0,69, - 69,74,74,3,0,10,10,48,48,87,87,2,0,39,39,51,51,1,0,103,104,2,0,114, - 114,134,134,7,0,20,20,36,36,53,54,68,68,76,76,93,93,99,99,12,0,1, - 19,21,28,30,35,37,40,42,49,51,52,56,56,58,67,69,75,77,92,94,95,97, - 98,4,0,19,19,28,28,37,37,46,46,1288,0,169,1,0,0,0,2,176,1,0,0,0, - 4,178,1,0,0,0,6,180,1,0,0,0,8,189,1,0,0,0,10,195,1,0,0,0,12,212, - 1,0,0,0,14,214,1,0,0,0,16,217,1,0,0,0,18,226,1,0,0,0,20,232,1,0, - 0,0,22,236,1,0,0,0,24,245,1,0,0,0,26,247,1,0,0,0,28,256,1,0,0,0, - 30,260,1,0,0,0,32,271,1,0,0,0,34,275,1,0,0,0,36,290,1,0,0,0,38,293, - 1,0,0,0,40,342,1,0,0,0,42,345,1,0,0,0,44,351,1,0,0,0,46,355,1,0, - 0,0,48,361,1,0,0,0,50,379,1,0,0,0,52,382,1,0,0,0,54,385,1,0,0,0, - 56,395,1,0,0,0,58,398,1,0,0,0,60,402,1,0,0,0,62,435,1,0,0,0,64,437, - 1,0,0,0,66,440,1,0,0,0,68,455,1,0,0,0,70,517,1,0,0,0,72,522,1,0, - 0,0,74,533,1,0,0,0,76,535,1,0,0,0,78,541,1,0,0,0,80,549,1,0,0,0, - 82,567,1,0,0,0,84,569,1,0,0,0,86,577,1,0,0,0,88,582,1,0,0,0,90,590, - 1,0,0,0,92,594,1,0,0,0,94,598,1,0,0,0,96,607,1,0,0,0,98,621,1,0, - 0,0,100,623,1,0,0,0,102,673,1,0,0,0,104,675,1,0,0,0,106,812,1,0, - 0,0,108,910,1,0,0,0,110,920,1,0,0,0,112,941,1,0,0,0,114,974,1,0, - 0,0,116,987,1,0,0,0,118,989,1,0,0,0,120,1007,1,0,0,0,122,1016,1, - 0,0,0,124,1018,1,0,0,0,126,1035,1,0,0,0,128,1048,1,0,0,0,130,1058, - 1,0,0,0,132,1062,1,0,0,0,134,1070,1,0,0,0,136,1080,1,0,0,0,138,1083, - 1,0,0,0,140,1096,1,0,0,0,142,1098,1,0,0,0,144,1100,1,0,0,0,146,1102, - 1,0,0,0,148,1106,1,0,0,0,150,1111,1,0,0,0,152,1113,1,0,0,0,154,1117, - 1,0,0,0,156,1123,1,0,0,0,158,1125,1,0,0,0,160,1139,1,0,0,0,162,1141, - 1,0,0,0,164,1155,1,0,0,0,166,168,3,2,1,0,167,166,1,0,0,0,168,171, + 3,53,915,8,53,1,53,1,53,1,53,1,53,1,53,1,53,1,53,3,53,924,8,53,5, + 53,926,8,53,10,53,12,53,929,9,53,1,54,1,54,1,54,5,54,934,8,54,10, + 54,12,54,937,9,54,1,55,1,55,3,55,941,8,55,1,56,1,56,1,56,1,56,5, + 56,947,8,56,10,56,12,56,950,9,56,1,56,1,56,1,56,1,56,1,56,5,56,957, + 8,56,10,56,12,56,960,9,56,3,56,962,8,56,1,56,1,56,1,56,1,57,1,57, + 1,57,5,57,970,8,57,10,57,12,57,973,9,57,1,57,1,57,1,57,1,57,1,57, + 1,57,5,57,981,8,57,10,57,12,57,984,9,57,1,57,1,57,3,57,988,8,57, + 1,57,1,57,1,57,1,57,1,57,3,57,995,8,57,1,58,1,58,1,58,1,58,1,58, + 1,58,1,58,1,58,1,58,1,58,1,58,3,58,1008,8,58,1,59,1,59,1,59,5,59, + 1013,8,59,10,59,12,59,1016,9,59,1,60,1,60,1,60,1,60,1,60,1,60,1, + 60,1,60,1,60,1,60,3,60,1028,8,60,1,61,1,61,1,61,1,61,3,61,1034,8, + 61,1,61,3,61,1037,8,61,1,62,1,62,1,62,5,62,1042,8,62,10,62,12,62, + 1045,9,62,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,1,63,3,63,1056, + 8,63,1,63,1,63,1,63,1,63,3,63,1062,8,63,5,63,1064,8,63,10,63,12, + 63,1067,9,63,1,64,1,64,1,64,3,64,1072,8,64,1,64,1,64,1,65,1,65,1, + 65,3,65,1079,8,65,1,65,1,65,1,66,1,66,1,66,5,66,1086,8,66,10,66, + 12,66,1089,9,66,1,67,1,67,1,68,1,68,1,68,1,68,1,68,1,68,3,68,1099, + 8,68,3,68,1101,8,68,1,69,3,69,1104,8,69,1,69,1,69,1,69,1,69,1,69, + 1,69,3,69,1112,8,69,1,70,1,70,1,70,3,70,1117,8,70,1,71,1,71,1,72, + 1,72,1,73,1,73,1,74,1,74,3,74,1127,8,74,1,75,1,75,1,75,3,75,1132, + 8,75,1,76,1,76,1,76,1,76,1,77,1,77,1,77,1,77,1,78,1,78,3,78,1144, + 8,78,1,79,1,79,5,79,1148,8,79,10,79,12,79,1151,9,79,1,79,1,79,1, + 80,1,80,1,80,1,80,1,80,3,80,1160,8,80,1,81,1,81,5,81,1164,8,81,10, + 81,12,81,1167,9,81,1,81,1,81,1,82,1,82,1,82,1,82,1,82,3,82,1176, + 8,82,1,82,0,3,68,106,126,83,0,2,4,6,8,10,12,14,16,18,20,22,24,26, + 28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70, + 72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110, + 112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142, + 144,146,148,150,152,154,156,158,160,162,164,0,16,2,0,17,17,72,72, + 2,0,42,42,49,49,3,0,1,1,4,4,8,8,4,0,1,1,3,4,8,8,78,78,2,0,49,49, + 71,71,2,0,1,1,4,4,2,0,7,7,21,22,2,0,28,28,47,47,2,0,69,69,74,74, + 3,0,10,10,48,48,87,87,2,0,39,39,51,51,1,0,103,104,2,0,114,114,134, + 134,7,0,20,20,36,36,53,54,68,68,76,76,93,93,99,99,12,0,1,19,21,28, + 30,35,37,40,42,49,51,52,56,56,58,67,69,75,77,92,94,95,97,98,4,0, + 19,19,28,28,37,37,46,46,1314,0,169,1,0,0,0,2,176,1,0,0,0,4,178,1, + 0,0,0,6,180,1,0,0,0,8,189,1,0,0,0,10,195,1,0,0,0,12,212,1,0,0,0, + 14,214,1,0,0,0,16,217,1,0,0,0,18,226,1,0,0,0,20,232,1,0,0,0,22,236, + 1,0,0,0,24,245,1,0,0,0,26,247,1,0,0,0,28,256,1,0,0,0,30,260,1,0, + 0,0,32,271,1,0,0,0,34,275,1,0,0,0,36,290,1,0,0,0,38,293,1,0,0,0, + 40,342,1,0,0,0,42,345,1,0,0,0,44,351,1,0,0,0,46,355,1,0,0,0,48,361, + 1,0,0,0,50,379,1,0,0,0,52,382,1,0,0,0,54,385,1,0,0,0,56,395,1,0, + 0,0,58,398,1,0,0,0,60,402,1,0,0,0,62,435,1,0,0,0,64,437,1,0,0,0, + 66,440,1,0,0,0,68,455,1,0,0,0,70,517,1,0,0,0,72,522,1,0,0,0,74,533, + 1,0,0,0,76,535,1,0,0,0,78,541,1,0,0,0,80,549,1,0,0,0,82,567,1,0, + 0,0,84,569,1,0,0,0,86,577,1,0,0,0,88,582,1,0,0,0,90,590,1,0,0,0, + 92,594,1,0,0,0,94,598,1,0,0,0,96,607,1,0,0,0,98,621,1,0,0,0,100, + 623,1,0,0,0,102,673,1,0,0,0,104,675,1,0,0,0,106,832,1,0,0,0,108, + 930,1,0,0,0,110,940,1,0,0,0,112,961,1,0,0,0,114,994,1,0,0,0,116, + 1007,1,0,0,0,118,1009,1,0,0,0,120,1027,1,0,0,0,122,1036,1,0,0,0, + 124,1038,1,0,0,0,126,1055,1,0,0,0,128,1068,1,0,0,0,130,1078,1,0, + 0,0,132,1082,1,0,0,0,134,1090,1,0,0,0,136,1100,1,0,0,0,138,1103, + 1,0,0,0,140,1116,1,0,0,0,142,1118,1,0,0,0,144,1120,1,0,0,0,146,1122, + 1,0,0,0,148,1126,1,0,0,0,150,1131,1,0,0,0,152,1133,1,0,0,0,154,1137, + 1,0,0,0,156,1143,1,0,0,0,158,1145,1,0,0,0,160,1159,1,0,0,0,162,1161, + 1,0,0,0,164,1175,1,0,0,0,166,168,3,2,1,0,167,166,1,0,0,0,168,171, 1,0,0,0,169,167,1,0,0,0,169,170,1,0,0,0,170,172,1,0,0,0,171,169, 1,0,0,0,172,173,5,0,0,1,173,1,1,0,0,0,174,177,3,6,3,0,175,177,3, 12,6,0,176,174,1,0,0,0,176,175,1,0,0,0,177,3,1,0,0,0,178,179,3,106, @@ -302,170 +304,178 @@ def serializedATN(): 690,3,106,53,0,690,691,5,81,0,0,691,692,3,106,53,0,692,694,1,0,0, 0,693,688,1,0,0,0,694,695,1,0,0,0,695,693,1,0,0,0,695,696,1,0,0, 0,696,699,1,0,0,0,697,698,5,24,0,0,698,700,3,106,53,0,699,697,1, - 0,0,0,699,700,1,0,0,0,700,701,1,0,0,0,701,702,5,25,0,0,702,813,1, + 0,0,0,699,700,1,0,0,0,700,701,1,0,0,0,701,702,5,25,0,0,702,833,1, 0,0,0,703,704,5,13,0,0,704,705,5,126,0,0,705,706,3,106,53,0,706, - 707,5,6,0,0,707,708,3,102,51,0,708,709,5,144,0,0,709,813,1,0,0,0, - 710,711,5,19,0,0,711,813,5,106,0,0,712,713,5,43,0,0,713,714,3,106, - 53,0,714,715,3,142,71,0,715,813,1,0,0,0,716,717,5,80,0,0,717,718, + 707,5,6,0,0,707,708,3,102,51,0,708,709,5,144,0,0,709,833,1,0,0,0, + 710,711,5,19,0,0,711,833,5,106,0,0,712,713,5,43,0,0,713,714,3,106, + 53,0,714,715,3,142,71,0,715,833,1,0,0,0,716,717,5,80,0,0,717,718, 5,126,0,0,718,719,3,106,53,0,719,720,5,32,0,0,720,723,3,106,53,0, 721,722,5,31,0,0,722,724,3,106,53,0,723,721,1,0,0,0,723,724,1,0, - 0,0,724,725,1,0,0,0,725,726,5,144,0,0,726,813,1,0,0,0,727,728,5, - 83,0,0,728,813,5,106,0,0,729,730,5,88,0,0,730,731,5,126,0,0,731, + 0,0,724,725,1,0,0,0,725,726,5,144,0,0,726,833,1,0,0,0,727,728,5, + 83,0,0,728,833,5,106,0,0,729,730,5,88,0,0,730,731,5,126,0,0,731, 732,7,9,0,0,732,733,3,156,78,0,733,734,5,32,0,0,734,735,3,106,53, - 0,735,736,5,144,0,0,736,813,1,0,0,0,737,738,3,150,75,0,738,740,5, + 0,735,736,5,144,0,0,736,833,1,0,0,0,737,738,3,150,75,0,738,740,5, 126,0,0,739,741,3,104,52,0,740,739,1,0,0,0,740,741,1,0,0,0,741,742, - 1,0,0,0,742,743,5,144,0,0,743,744,1,0,0,0,744,745,5,64,0,0,745,746, - 5,126,0,0,746,747,3,88,44,0,747,748,5,144,0,0,748,813,1,0,0,0,749, - 750,3,150,75,0,750,752,5,126,0,0,751,753,3,104,52,0,752,751,1,0, - 0,0,752,753,1,0,0,0,753,754,1,0,0,0,754,755,5,144,0,0,755,756,1, - 0,0,0,756,757,5,64,0,0,757,758,3,150,75,0,758,813,1,0,0,0,759,765, - 3,150,75,0,760,762,5,126,0,0,761,763,3,104,52,0,762,761,1,0,0,0, - 762,763,1,0,0,0,763,764,1,0,0,0,764,766,5,144,0,0,765,760,1,0,0, - 0,765,766,1,0,0,0,766,767,1,0,0,0,767,769,5,126,0,0,768,770,5,23, - 0,0,769,768,1,0,0,0,769,770,1,0,0,0,770,772,1,0,0,0,771,773,3,108, - 54,0,772,771,1,0,0,0,772,773,1,0,0,0,773,774,1,0,0,0,774,775,5,144, - 0,0,775,813,1,0,0,0,776,813,3,114,57,0,777,813,3,158,79,0,778,813, - 3,140,70,0,779,780,5,114,0,0,780,813,3,106,53,19,781,782,5,56,0, - 0,782,813,3,106,53,13,783,784,3,130,65,0,784,785,5,116,0,0,785,787, - 1,0,0,0,786,783,1,0,0,0,786,787,1,0,0,0,787,788,1,0,0,0,788,813, - 5,108,0,0,789,790,5,126,0,0,790,791,3,34,17,0,791,792,5,144,0,0, - 792,813,1,0,0,0,793,794,5,126,0,0,794,795,3,106,53,0,795,796,5,144, - 0,0,796,813,1,0,0,0,797,798,5,126,0,0,798,799,3,104,52,0,799,800, - 5,144,0,0,800,813,1,0,0,0,801,803,5,125,0,0,802,804,3,104,52,0,803, - 802,1,0,0,0,803,804,1,0,0,0,804,805,1,0,0,0,805,813,5,143,0,0,806, - 808,5,124,0,0,807,809,3,30,15,0,808,807,1,0,0,0,808,809,1,0,0,0, - 809,810,1,0,0,0,810,813,5,142,0,0,811,813,3,122,61,0,812,683,1,0, - 0,0,812,703,1,0,0,0,812,710,1,0,0,0,812,712,1,0,0,0,812,716,1,0, - 0,0,812,727,1,0,0,0,812,729,1,0,0,0,812,737,1,0,0,0,812,749,1,0, - 0,0,812,759,1,0,0,0,812,776,1,0,0,0,812,777,1,0,0,0,812,778,1,0, - 0,0,812,779,1,0,0,0,812,781,1,0,0,0,812,786,1,0,0,0,812,789,1,0, - 0,0,812,793,1,0,0,0,812,797,1,0,0,0,812,801,1,0,0,0,812,806,1,0, - 0,0,812,811,1,0,0,0,813,907,1,0,0,0,814,818,10,18,0,0,815,819,5, - 108,0,0,816,819,5,146,0,0,817,819,5,133,0,0,818,815,1,0,0,0,818, - 816,1,0,0,0,818,817,1,0,0,0,819,820,1,0,0,0,820,906,3,106,53,19, - 821,825,10,17,0,0,822,826,5,134,0,0,823,826,5,114,0,0,824,826,5, - 113,0,0,825,822,1,0,0,0,825,823,1,0,0,0,825,824,1,0,0,0,826,827, - 1,0,0,0,827,906,3,106,53,18,828,853,10,16,0,0,829,854,5,117,0,0, - 830,854,5,118,0,0,831,854,5,129,0,0,832,854,5,127,0,0,833,854,5, - 128,0,0,834,854,5,119,0,0,835,854,5,120,0,0,836,838,5,56,0,0,837, - 836,1,0,0,0,837,838,1,0,0,0,838,839,1,0,0,0,839,841,5,40,0,0,840, - 842,5,14,0,0,841,840,1,0,0,0,841,842,1,0,0,0,842,854,1,0,0,0,843, - 845,5,56,0,0,844,843,1,0,0,0,844,845,1,0,0,0,845,846,1,0,0,0,846, - 854,7,10,0,0,847,854,5,140,0,0,848,854,5,141,0,0,849,854,5,131,0, - 0,850,854,5,122,0,0,851,854,5,123,0,0,852,854,5,130,0,0,853,829, - 1,0,0,0,853,830,1,0,0,0,853,831,1,0,0,0,853,832,1,0,0,0,853,833, - 1,0,0,0,853,834,1,0,0,0,853,835,1,0,0,0,853,837,1,0,0,0,853,844, - 1,0,0,0,853,847,1,0,0,0,853,848,1,0,0,0,853,849,1,0,0,0,853,850, - 1,0,0,0,853,851,1,0,0,0,853,852,1,0,0,0,854,855,1,0,0,0,855,906, - 3,106,53,17,856,857,10,14,0,0,857,858,5,132,0,0,858,906,3,106,53, - 15,859,860,10,12,0,0,860,861,5,2,0,0,861,906,3,106,53,13,862,863, - 10,11,0,0,863,864,5,61,0,0,864,906,3,106,53,12,865,867,10,10,0,0, - 866,868,5,56,0,0,867,866,1,0,0,0,867,868,1,0,0,0,868,869,1,0,0,0, - 869,870,5,9,0,0,870,871,3,106,53,0,871,872,5,2,0,0,872,873,3,106, - 53,11,873,906,1,0,0,0,874,875,10,9,0,0,875,876,5,135,0,0,876,877, - 3,106,53,0,877,878,5,111,0,0,878,879,3,106,53,9,879,906,1,0,0,0, - 880,881,10,22,0,0,881,882,5,125,0,0,882,883,3,106,53,0,883,884,5, - 143,0,0,884,906,1,0,0,0,885,886,10,21,0,0,886,887,5,116,0,0,887, - 906,5,104,0,0,888,889,10,20,0,0,889,890,5,116,0,0,890,906,3,150, - 75,0,891,892,10,15,0,0,892,894,5,44,0,0,893,895,5,56,0,0,894,893, - 1,0,0,0,894,895,1,0,0,0,895,896,1,0,0,0,896,906,5,57,0,0,897,903, - 10,8,0,0,898,904,3,148,74,0,899,900,5,6,0,0,900,904,3,150,75,0,901, - 902,5,6,0,0,902,904,5,106,0,0,903,898,1,0,0,0,903,899,1,0,0,0,903, - 901,1,0,0,0,904,906,1,0,0,0,905,814,1,0,0,0,905,821,1,0,0,0,905, - 828,1,0,0,0,905,856,1,0,0,0,905,859,1,0,0,0,905,862,1,0,0,0,905, - 865,1,0,0,0,905,874,1,0,0,0,905,880,1,0,0,0,905,885,1,0,0,0,905, - 888,1,0,0,0,905,891,1,0,0,0,905,897,1,0,0,0,906,909,1,0,0,0,907, - 905,1,0,0,0,907,908,1,0,0,0,908,107,1,0,0,0,909,907,1,0,0,0,910, - 915,3,110,55,0,911,912,5,112,0,0,912,914,3,110,55,0,913,911,1,0, - 0,0,914,917,1,0,0,0,915,913,1,0,0,0,915,916,1,0,0,0,916,109,1,0, - 0,0,917,915,1,0,0,0,918,921,3,112,56,0,919,921,3,106,53,0,920,918, - 1,0,0,0,920,919,1,0,0,0,921,111,1,0,0,0,922,923,5,126,0,0,923,928, - 3,150,75,0,924,925,5,112,0,0,925,927,3,150,75,0,926,924,1,0,0,0, - 927,930,1,0,0,0,928,926,1,0,0,0,928,929,1,0,0,0,929,931,1,0,0,0, - 930,928,1,0,0,0,931,932,5,144,0,0,932,942,1,0,0,0,933,938,3,150, - 75,0,934,935,5,112,0,0,935,937,3,150,75,0,936,934,1,0,0,0,937,940, - 1,0,0,0,938,936,1,0,0,0,938,939,1,0,0,0,939,942,1,0,0,0,940,938, - 1,0,0,0,941,922,1,0,0,0,941,933,1,0,0,0,942,943,1,0,0,0,943,944, - 5,107,0,0,944,945,3,106,53,0,945,113,1,0,0,0,946,947,5,128,0,0,947, - 951,3,150,75,0,948,950,3,116,58,0,949,948,1,0,0,0,950,953,1,0,0, - 0,951,949,1,0,0,0,951,952,1,0,0,0,952,954,1,0,0,0,953,951,1,0,0, - 0,954,955,5,146,0,0,955,956,5,120,0,0,956,975,1,0,0,0,957,958,5, - 128,0,0,958,962,3,150,75,0,959,961,3,116,58,0,960,959,1,0,0,0,961, - 964,1,0,0,0,962,960,1,0,0,0,962,963,1,0,0,0,963,965,1,0,0,0,964, - 962,1,0,0,0,965,967,5,120,0,0,966,968,3,114,57,0,967,966,1,0,0,0, - 967,968,1,0,0,0,968,969,1,0,0,0,969,970,5,128,0,0,970,971,5,146, - 0,0,971,972,3,150,75,0,972,973,5,120,0,0,973,975,1,0,0,0,974,946, - 1,0,0,0,974,957,1,0,0,0,975,115,1,0,0,0,976,977,3,150,75,0,977,978, - 5,118,0,0,978,979,3,156,78,0,979,988,1,0,0,0,980,981,3,150,75,0, - 981,982,5,118,0,0,982,983,5,124,0,0,983,984,3,106,53,0,984,985,5, - 142,0,0,985,988,1,0,0,0,986,988,3,150,75,0,987,976,1,0,0,0,987,980, - 1,0,0,0,987,986,1,0,0,0,988,117,1,0,0,0,989,994,3,120,60,0,990,991, - 5,112,0,0,991,993,3,120,60,0,992,990,1,0,0,0,993,996,1,0,0,0,994, - 992,1,0,0,0,994,995,1,0,0,0,995,119,1,0,0,0,996,994,1,0,0,0,997, - 998,3,150,75,0,998,999,5,6,0,0,999,1000,5,126,0,0,1000,1001,3,34, - 17,0,1001,1002,5,144,0,0,1002,1008,1,0,0,0,1003,1004,3,106,53,0, - 1004,1005,5,6,0,0,1005,1006,3,150,75,0,1006,1008,1,0,0,0,1007,997, - 1,0,0,0,1007,1003,1,0,0,0,1008,121,1,0,0,0,1009,1017,3,154,77,0, - 1010,1011,3,130,65,0,1011,1012,5,116,0,0,1012,1014,1,0,0,0,1013, - 1010,1,0,0,0,1013,1014,1,0,0,0,1014,1015,1,0,0,0,1015,1017,3,124, - 62,0,1016,1009,1,0,0,0,1016,1013,1,0,0,0,1017,123,1,0,0,0,1018,1023, - 3,150,75,0,1019,1020,5,116,0,0,1020,1022,3,150,75,0,1021,1019,1, - 0,0,0,1022,1025,1,0,0,0,1023,1021,1,0,0,0,1023,1024,1,0,0,0,1024, - 125,1,0,0,0,1025,1023,1,0,0,0,1026,1027,6,63,-1,0,1027,1036,3,130, - 65,0,1028,1036,3,128,64,0,1029,1030,5,126,0,0,1030,1031,3,34,17, - 0,1031,1032,5,144,0,0,1032,1036,1,0,0,0,1033,1036,3,114,57,0,1034, - 1036,3,154,77,0,1035,1026,1,0,0,0,1035,1028,1,0,0,0,1035,1029,1, - 0,0,0,1035,1033,1,0,0,0,1035,1034,1,0,0,0,1036,1045,1,0,0,0,1037, - 1041,10,3,0,0,1038,1042,3,148,74,0,1039,1040,5,6,0,0,1040,1042,3, - 150,75,0,1041,1038,1,0,0,0,1041,1039,1,0,0,0,1042,1044,1,0,0,0,1043, - 1037,1,0,0,0,1044,1047,1,0,0,0,1045,1043,1,0,0,0,1045,1046,1,0,0, - 0,1046,127,1,0,0,0,1047,1045,1,0,0,0,1048,1049,3,150,75,0,1049,1051, - 5,126,0,0,1050,1052,3,132,66,0,1051,1050,1,0,0,0,1051,1052,1,0,0, - 0,1052,1053,1,0,0,0,1053,1054,5,144,0,0,1054,129,1,0,0,0,1055,1056, - 3,134,67,0,1056,1057,5,116,0,0,1057,1059,1,0,0,0,1058,1055,1,0,0, - 0,1058,1059,1,0,0,0,1059,1060,1,0,0,0,1060,1061,3,150,75,0,1061, - 131,1,0,0,0,1062,1067,3,106,53,0,1063,1064,5,112,0,0,1064,1066,3, - 106,53,0,1065,1063,1,0,0,0,1066,1069,1,0,0,0,1067,1065,1,0,0,0,1067, - 1068,1,0,0,0,1068,133,1,0,0,0,1069,1067,1,0,0,0,1070,1071,3,150, - 75,0,1071,135,1,0,0,0,1072,1081,5,102,0,0,1073,1074,5,116,0,0,1074, - 1081,7,11,0,0,1075,1076,5,104,0,0,1076,1078,5,116,0,0,1077,1079, - 7,11,0,0,1078,1077,1,0,0,0,1078,1079,1,0,0,0,1079,1081,1,0,0,0,1080, - 1072,1,0,0,0,1080,1073,1,0,0,0,1080,1075,1,0,0,0,1081,137,1,0,0, - 0,1082,1084,7,12,0,0,1083,1082,1,0,0,0,1083,1084,1,0,0,0,1084,1091, - 1,0,0,0,1085,1092,3,136,68,0,1086,1092,5,103,0,0,1087,1092,5,104, - 0,0,1088,1092,5,105,0,0,1089,1092,5,41,0,0,1090,1092,5,55,0,0,1091, - 1085,1,0,0,0,1091,1086,1,0,0,0,1091,1087,1,0,0,0,1091,1088,1,0,0, - 0,1091,1089,1,0,0,0,1091,1090,1,0,0,0,1092,139,1,0,0,0,1093,1097, - 3,138,69,0,1094,1097,5,106,0,0,1095,1097,5,57,0,0,1096,1093,1,0, - 0,0,1096,1094,1,0,0,0,1096,1095,1,0,0,0,1097,141,1,0,0,0,1098,1099, - 7,13,0,0,1099,143,1,0,0,0,1100,1101,7,14,0,0,1101,145,1,0,0,0,1102, - 1103,7,15,0,0,1103,147,1,0,0,0,1104,1107,5,101,0,0,1105,1107,3,146, - 73,0,1106,1104,1,0,0,0,1106,1105,1,0,0,0,1107,149,1,0,0,0,1108,1112, - 5,101,0,0,1109,1112,3,142,71,0,1110,1112,3,144,72,0,1111,1108,1, - 0,0,0,1111,1109,1,0,0,0,1111,1110,1,0,0,0,1112,151,1,0,0,0,1113, - 1114,3,156,78,0,1114,1115,5,118,0,0,1115,1116,3,138,69,0,1116,153, - 1,0,0,0,1117,1118,5,124,0,0,1118,1119,3,150,75,0,1119,1120,5,142, - 0,0,1120,155,1,0,0,0,1121,1124,5,106,0,0,1122,1124,3,158,79,0,1123, - 1121,1,0,0,0,1123,1122,1,0,0,0,1124,157,1,0,0,0,1125,1129,5,137, - 0,0,1126,1128,3,160,80,0,1127,1126,1,0,0,0,1128,1131,1,0,0,0,1129, - 1127,1,0,0,0,1129,1130,1,0,0,0,1130,1132,1,0,0,0,1131,1129,1,0,0, - 0,1132,1133,5,139,0,0,1133,159,1,0,0,0,1134,1135,5,152,0,0,1135, - 1136,3,106,53,0,1136,1137,5,142,0,0,1137,1140,1,0,0,0,1138,1140, - 5,151,0,0,1139,1134,1,0,0,0,1139,1138,1,0,0,0,1140,161,1,0,0,0,1141, - 1145,5,138,0,0,1142,1144,3,164,82,0,1143,1142,1,0,0,0,1144,1147, - 1,0,0,0,1145,1143,1,0,0,0,1145,1146,1,0,0,0,1146,1148,1,0,0,0,1147, - 1145,1,0,0,0,1148,1149,5,0,0,1,1149,163,1,0,0,0,1150,1151,5,154, - 0,0,1151,1152,3,106,53,0,1152,1153,5,142,0,0,1153,1156,1,0,0,0,1154, - 1156,5,153,0,0,1155,1150,1,0,0,0,1155,1154,1,0,0,0,1156,165,1,0, - 0,0,135,169,176,185,200,212,224,240,251,265,271,281,290,293,297, + 1,0,0,0,742,743,5,144,0,0,743,752,1,0,0,0,744,746,5,126,0,0,745, + 747,5,23,0,0,746,745,1,0,0,0,746,747,1,0,0,0,747,749,1,0,0,0,748, + 750,3,108,54,0,749,748,1,0,0,0,749,750,1,0,0,0,750,751,1,0,0,0,751, + 753,5,144,0,0,752,744,1,0,0,0,752,753,1,0,0,0,753,754,1,0,0,0,754, + 755,5,64,0,0,755,756,5,126,0,0,756,757,3,88,44,0,757,758,5,144,0, + 0,758,833,1,0,0,0,759,760,3,150,75,0,760,762,5,126,0,0,761,763,3, + 104,52,0,762,761,1,0,0,0,762,763,1,0,0,0,763,764,1,0,0,0,764,765, + 5,144,0,0,765,774,1,0,0,0,766,768,5,126,0,0,767,769,5,23,0,0,768, + 767,1,0,0,0,768,769,1,0,0,0,769,771,1,0,0,0,770,772,3,108,54,0,771, + 770,1,0,0,0,771,772,1,0,0,0,772,773,1,0,0,0,773,775,5,144,0,0,774, + 766,1,0,0,0,774,775,1,0,0,0,775,776,1,0,0,0,776,777,5,64,0,0,777, + 778,3,150,75,0,778,833,1,0,0,0,779,785,3,150,75,0,780,782,5,126, + 0,0,781,783,3,104,52,0,782,781,1,0,0,0,782,783,1,0,0,0,783,784,1, + 0,0,0,784,786,5,144,0,0,785,780,1,0,0,0,785,786,1,0,0,0,786,787, + 1,0,0,0,787,789,5,126,0,0,788,790,5,23,0,0,789,788,1,0,0,0,789,790, + 1,0,0,0,790,792,1,0,0,0,791,793,3,108,54,0,792,791,1,0,0,0,792,793, + 1,0,0,0,793,794,1,0,0,0,794,795,5,144,0,0,795,833,1,0,0,0,796,833, + 3,114,57,0,797,833,3,158,79,0,798,833,3,140,70,0,799,800,5,114,0, + 0,800,833,3,106,53,19,801,802,5,56,0,0,802,833,3,106,53,13,803,804, + 3,130,65,0,804,805,5,116,0,0,805,807,1,0,0,0,806,803,1,0,0,0,806, + 807,1,0,0,0,807,808,1,0,0,0,808,833,5,108,0,0,809,810,5,126,0,0, + 810,811,3,34,17,0,811,812,5,144,0,0,812,833,1,0,0,0,813,814,5,126, + 0,0,814,815,3,106,53,0,815,816,5,144,0,0,816,833,1,0,0,0,817,818, + 5,126,0,0,818,819,3,104,52,0,819,820,5,144,0,0,820,833,1,0,0,0,821, + 823,5,125,0,0,822,824,3,104,52,0,823,822,1,0,0,0,823,824,1,0,0,0, + 824,825,1,0,0,0,825,833,5,143,0,0,826,828,5,124,0,0,827,829,3,30, + 15,0,828,827,1,0,0,0,828,829,1,0,0,0,829,830,1,0,0,0,830,833,5,142, + 0,0,831,833,3,122,61,0,832,683,1,0,0,0,832,703,1,0,0,0,832,710,1, + 0,0,0,832,712,1,0,0,0,832,716,1,0,0,0,832,727,1,0,0,0,832,729,1, + 0,0,0,832,737,1,0,0,0,832,759,1,0,0,0,832,779,1,0,0,0,832,796,1, + 0,0,0,832,797,1,0,0,0,832,798,1,0,0,0,832,799,1,0,0,0,832,801,1, + 0,0,0,832,806,1,0,0,0,832,809,1,0,0,0,832,813,1,0,0,0,832,817,1, + 0,0,0,832,821,1,0,0,0,832,826,1,0,0,0,832,831,1,0,0,0,833,927,1, + 0,0,0,834,838,10,18,0,0,835,839,5,108,0,0,836,839,5,146,0,0,837, + 839,5,133,0,0,838,835,1,0,0,0,838,836,1,0,0,0,838,837,1,0,0,0,839, + 840,1,0,0,0,840,926,3,106,53,19,841,845,10,17,0,0,842,846,5,134, + 0,0,843,846,5,114,0,0,844,846,5,113,0,0,845,842,1,0,0,0,845,843, + 1,0,0,0,845,844,1,0,0,0,846,847,1,0,0,0,847,926,3,106,53,18,848, + 873,10,16,0,0,849,874,5,117,0,0,850,874,5,118,0,0,851,874,5,129, + 0,0,852,874,5,127,0,0,853,874,5,128,0,0,854,874,5,119,0,0,855,874, + 5,120,0,0,856,858,5,56,0,0,857,856,1,0,0,0,857,858,1,0,0,0,858,859, + 1,0,0,0,859,861,5,40,0,0,860,862,5,14,0,0,861,860,1,0,0,0,861,862, + 1,0,0,0,862,874,1,0,0,0,863,865,5,56,0,0,864,863,1,0,0,0,864,865, + 1,0,0,0,865,866,1,0,0,0,866,874,7,10,0,0,867,874,5,140,0,0,868,874, + 5,141,0,0,869,874,5,131,0,0,870,874,5,122,0,0,871,874,5,123,0,0, + 872,874,5,130,0,0,873,849,1,0,0,0,873,850,1,0,0,0,873,851,1,0,0, + 0,873,852,1,0,0,0,873,853,1,0,0,0,873,854,1,0,0,0,873,855,1,0,0, + 0,873,857,1,0,0,0,873,864,1,0,0,0,873,867,1,0,0,0,873,868,1,0,0, + 0,873,869,1,0,0,0,873,870,1,0,0,0,873,871,1,0,0,0,873,872,1,0,0, + 0,874,875,1,0,0,0,875,926,3,106,53,17,876,877,10,14,0,0,877,878, + 5,132,0,0,878,926,3,106,53,15,879,880,10,12,0,0,880,881,5,2,0,0, + 881,926,3,106,53,13,882,883,10,11,0,0,883,884,5,61,0,0,884,926,3, + 106,53,12,885,887,10,10,0,0,886,888,5,56,0,0,887,886,1,0,0,0,887, + 888,1,0,0,0,888,889,1,0,0,0,889,890,5,9,0,0,890,891,3,106,53,0,891, + 892,5,2,0,0,892,893,3,106,53,11,893,926,1,0,0,0,894,895,10,9,0,0, + 895,896,5,135,0,0,896,897,3,106,53,0,897,898,5,111,0,0,898,899,3, + 106,53,9,899,926,1,0,0,0,900,901,10,22,0,0,901,902,5,125,0,0,902, + 903,3,106,53,0,903,904,5,143,0,0,904,926,1,0,0,0,905,906,10,21,0, + 0,906,907,5,116,0,0,907,926,5,104,0,0,908,909,10,20,0,0,909,910, + 5,116,0,0,910,926,3,150,75,0,911,912,10,15,0,0,912,914,5,44,0,0, + 913,915,5,56,0,0,914,913,1,0,0,0,914,915,1,0,0,0,915,916,1,0,0,0, + 916,926,5,57,0,0,917,923,10,8,0,0,918,924,3,148,74,0,919,920,5,6, + 0,0,920,924,3,150,75,0,921,922,5,6,0,0,922,924,5,106,0,0,923,918, + 1,0,0,0,923,919,1,0,0,0,923,921,1,0,0,0,924,926,1,0,0,0,925,834, + 1,0,0,0,925,841,1,0,0,0,925,848,1,0,0,0,925,876,1,0,0,0,925,879, + 1,0,0,0,925,882,1,0,0,0,925,885,1,0,0,0,925,894,1,0,0,0,925,900, + 1,0,0,0,925,905,1,0,0,0,925,908,1,0,0,0,925,911,1,0,0,0,925,917, + 1,0,0,0,926,929,1,0,0,0,927,925,1,0,0,0,927,928,1,0,0,0,928,107, + 1,0,0,0,929,927,1,0,0,0,930,935,3,110,55,0,931,932,5,112,0,0,932, + 934,3,110,55,0,933,931,1,0,0,0,934,937,1,0,0,0,935,933,1,0,0,0,935, + 936,1,0,0,0,936,109,1,0,0,0,937,935,1,0,0,0,938,941,3,112,56,0,939, + 941,3,106,53,0,940,938,1,0,0,0,940,939,1,0,0,0,941,111,1,0,0,0,942, + 943,5,126,0,0,943,948,3,150,75,0,944,945,5,112,0,0,945,947,3,150, + 75,0,946,944,1,0,0,0,947,950,1,0,0,0,948,946,1,0,0,0,948,949,1,0, + 0,0,949,951,1,0,0,0,950,948,1,0,0,0,951,952,5,144,0,0,952,962,1, + 0,0,0,953,958,3,150,75,0,954,955,5,112,0,0,955,957,3,150,75,0,956, + 954,1,0,0,0,957,960,1,0,0,0,958,956,1,0,0,0,958,959,1,0,0,0,959, + 962,1,0,0,0,960,958,1,0,0,0,961,942,1,0,0,0,961,953,1,0,0,0,962, + 963,1,0,0,0,963,964,5,107,0,0,964,965,3,106,53,0,965,113,1,0,0,0, + 966,967,5,128,0,0,967,971,3,150,75,0,968,970,3,116,58,0,969,968, + 1,0,0,0,970,973,1,0,0,0,971,969,1,0,0,0,971,972,1,0,0,0,972,974, + 1,0,0,0,973,971,1,0,0,0,974,975,5,146,0,0,975,976,5,120,0,0,976, + 995,1,0,0,0,977,978,5,128,0,0,978,982,3,150,75,0,979,981,3,116,58, + 0,980,979,1,0,0,0,981,984,1,0,0,0,982,980,1,0,0,0,982,983,1,0,0, + 0,983,985,1,0,0,0,984,982,1,0,0,0,985,987,5,120,0,0,986,988,3,114, + 57,0,987,986,1,0,0,0,987,988,1,0,0,0,988,989,1,0,0,0,989,990,5,128, + 0,0,990,991,5,146,0,0,991,992,3,150,75,0,992,993,5,120,0,0,993,995, + 1,0,0,0,994,966,1,0,0,0,994,977,1,0,0,0,995,115,1,0,0,0,996,997, + 3,150,75,0,997,998,5,118,0,0,998,999,3,156,78,0,999,1008,1,0,0,0, + 1000,1001,3,150,75,0,1001,1002,5,118,0,0,1002,1003,5,124,0,0,1003, + 1004,3,106,53,0,1004,1005,5,142,0,0,1005,1008,1,0,0,0,1006,1008, + 3,150,75,0,1007,996,1,0,0,0,1007,1000,1,0,0,0,1007,1006,1,0,0,0, + 1008,117,1,0,0,0,1009,1014,3,120,60,0,1010,1011,5,112,0,0,1011,1013, + 3,120,60,0,1012,1010,1,0,0,0,1013,1016,1,0,0,0,1014,1012,1,0,0,0, + 1014,1015,1,0,0,0,1015,119,1,0,0,0,1016,1014,1,0,0,0,1017,1018,3, + 150,75,0,1018,1019,5,6,0,0,1019,1020,5,126,0,0,1020,1021,3,34,17, + 0,1021,1022,5,144,0,0,1022,1028,1,0,0,0,1023,1024,3,106,53,0,1024, + 1025,5,6,0,0,1025,1026,3,150,75,0,1026,1028,1,0,0,0,1027,1017,1, + 0,0,0,1027,1023,1,0,0,0,1028,121,1,0,0,0,1029,1037,3,154,77,0,1030, + 1031,3,130,65,0,1031,1032,5,116,0,0,1032,1034,1,0,0,0,1033,1030, + 1,0,0,0,1033,1034,1,0,0,0,1034,1035,1,0,0,0,1035,1037,3,124,62,0, + 1036,1029,1,0,0,0,1036,1033,1,0,0,0,1037,123,1,0,0,0,1038,1043,3, + 150,75,0,1039,1040,5,116,0,0,1040,1042,3,150,75,0,1041,1039,1,0, + 0,0,1042,1045,1,0,0,0,1043,1041,1,0,0,0,1043,1044,1,0,0,0,1044,125, + 1,0,0,0,1045,1043,1,0,0,0,1046,1047,6,63,-1,0,1047,1056,3,130,65, + 0,1048,1056,3,128,64,0,1049,1050,5,126,0,0,1050,1051,3,34,17,0,1051, + 1052,5,144,0,0,1052,1056,1,0,0,0,1053,1056,3,114,57,0,1054,1056, + 3,154,77,0,1055,1046,1,0,0,0,1055,1048,1,0,0,0,1055,1049,1,0,0,0, + 1055,1053,1,0,0,0,1055,1054,1,0,0,0,1056,1065,1,0,0,0,1057,1061, + 10,3,0,0,1058,1062,3,148,74,0,1059,1060,5,6,0,0,1060,1062,3,150, + 75,0,1061,1058,1,0,0,0,1061,1059,1,0,0,0,1062,1064,1,0,0,0,1063, + 1057,1,0,0,0,1064,1067,1,0,0,0,1065,1063,1,0,0,0,1065,1066,1,0,0, + 0,1066,127,1,0,0,0,1067,1065,1,0,0,0,1068,1069,3,150,75,0,1069,1071, + 5,126,0,0,1070,1072,3,132,66,0,1071,1070,1,0,0,0,1071,1072,1,0,0, + 0,1072,1073,1,0,0,0,1073,1074,5,144,0,0,1074,129,1,0,0,0,1075,1076, + 3,134,67,0,1076,1077,5,116,0,0,1077,1079,1,0,0,0,1078,1075,1,0,0, + 0,1078,1079,1,0,0,0,1079,1080,1,0,0,0,1080,1081,3,150,75,0,1081, + 131,1,0,0,0,1082,1087,3,106,53,0,1083,1084,5,112,0,0,1084,1086,3, + 106,53,0,1085,1083,1,0,0,0,1086,1089,1,0,0,0,1087,1085,1,0,0,0,1087, + 1088,1,0,0,0,1088,133,1,0,0,0,1089,1087,1,0,0,0,1090,1091,3,150, + 75,0,1091,135,1,0,0,0,1092,1101,5,102,0,0,1093,1094,5,116,0,0,1094, + 1101,7,11,0,0,1095,1096,5,104,0,0,1096,1098,5,116,0,0,1097,1099, + 7,11,0,0,1098,1097,1,0,0,0,1098,1099,1,0,0,0,1099,1101,1,0,0,0,1100, + 1092,1,0,0,0,1100,1093,1,0,0,0,1100,1095,1,0,0,0,1101,137,1,0,0, + 0,1102,1104,7,12,0,0,1103,1102,1,0,0,0,1103,1104,1,0,0,0,1104,1111, + 1,0,0,0,1105,1112,3,136,68,0,1106,1112,5,103,0,0,1107,1112,5,104, + 0,0,1108,1112,5,105,0,0,1109,1112,5,41,0,0,1110,1112,5,55,0,0,1111, + 1105,1,0,0,0,1111,1106,1,0,0,0,1111,1107,1,0,0,0,1111,1108,1,0,0, + 0,1111,1109,1,0,0,0,1111,1110,1,0,0,0,1112,139,1,0,0,0,1113,1117, + 3,138,69,0,1114,1117,5,106,0,0,1115,1117,5,57,0,0,1116,1113,1,0, + 0,0,1116,1114,1,0,0,0,1116,1115,1,0,0,0,1117,141,1,0,0,0,1118,1119, + 7,13,0,0,1119,143,1,0,0,0,1120,1121,7,14,0,0,1121,145,1,0,0,0,1122, + 1123,7,15,0,0,1123,147,1,0,0,0,1124,1127,5,101,0,0,1125,1127,3,146, + 73,0,1126,1124,1,0,0,0,1126,1125,1,0,0,0,1127,149,1,0,0,0,1128,1132, + 5,101,0,0,1129,1132,3,142,71,0,1130,1132,3,144,72,0,1131,1128,1, + 0,0,0,1131,1129,1,0,0,0,1131,1130,1,0,0,0,1132,151,1,0,0,0,1133, + 1134,3,156,78,0,1134,1135,5,118,0,0,1135,1136,3,138,69,0,1136,153, + 1,0,0,0,1137,1138,5,124,0,0,1138,1139,3,150,75,0,1139,1140,5,142, + 0,0,1140,155,1,0,0,0,1141,1144,5,106,0,0,1142,1144,3,158,79,0,1143, + 1141,1,0,0,0,1143,1142,1,0,0,0,1144,157,1,0,0,0,1145,1149,5,137, + 0,0,1146,1148,3,160,80,0,1147,1146,1,0,0,0,1148,1151,1,0,0,0,1149, + 1147,1,0,0,0,1149,1150,1,0,0,0,1150,1152,1,0,0,0,1151,1149,1,0,0, + 0,1152,1153,5,139,0,0,1153,159,1,0,0,0,1154,1155,5,152,0,0,1155, + 1156,3,106,53,0,1156,1157,5,142,0,0,1157,1160,1,0,0,0,1158,1160, + 5,151,0,0,1159,1154,1,0,0,0,1159,1158,1,0,0,0,1160,161,1,0,0,0,1161, + 1165,5,138,0,0,1162,1164,3,164,82,0,1163,1162,1,0,0,0,1164,1167, + 1,0,0,0,1165,1163,1,0,0,0,1165,1166,1,0,0,0,1166,1168,1,0,0,0,1167, + 1165,1,0,0,0,1168,1169,5,0,0,1,1169,163,1,0,0,0,1170,1171,5,154, + 0,0,1171,1172,3,106,53,0,1172,1173,5,142,0,0,1173,1176,1,0,0,0,1174, + 1176,5,153,0,0,1175,1170,1,0,0,0,1175,1174,1,0,0,0,1176,165,1,0, + 0,0,141,169,176,185,200,212,224,240,251,265,271,281,290,293,297, 300,304,307,310,313,316,320,324,327,330,333,337,340,349,355,376, 393,410,416,422,433,435,446,449,455,463,469,471,475,480,483,486, 490,494,497,499,502,506,510,513,515,517,522,533,539,546,551,555, 559,565,567,574,582,585,588,607,621,637,649,661,669,673,680,686, - 695,699,723,740,752,762,765,769,772,786,803,808,812,818,825,837, - 841,844,853,867,894,903,905,907,915,920,928,938,941,951,962,967, - 974,987,994,1007,1013,1016,1023,1035,1041,1045,1051,1058,1067,1078, - 1080,1083,1091,1096,1106,1111,1123,1129,1139,1145,1155 + 695,699,723,740,746,749,752,762,768,771,774,782,785,789,792,806, + 823,828,832,838,845,857,861,864,873,887,914,923,925,927,935,940, + 948,958,961,971,982,987,994,1007,1014,1027,1033,1036,1043,1055,1061, + 1065,1071,1078,1087,1098,1100,1103,1111,1116,1126,1131,1143,1149, + 1159,1165,1175 ] class HogQLParser ( Parser ): @@ -5467,13 +5477,24 @@ def identifier(self, i:int=None): def OVER(self): return self.getToken(HogQLParser.OVER, 0) - def LPAREN(self): - return self.getToken(HogQLParser.LPAREN, 0) - def RPAREN(self): - return self.getToken(HogQLParser.RPAREN, 0) + def LPAREN(self, i:int=None): + if i is None: + return self.getTokens(HogQLParser.LPAREN) + else: + return self.getToken(HogQLParser.LPAREN, i) + def RPAREN(self, i:int=None): + if i is None: + return self.getTokens(HogQLParser.RPAREN) + else: + return self.getToken(HogQLParser.RPAREN, i) def columnExprList(self): return self.getTypedRuleContext(HogQLParser.ColumnExprListContext,0) + def DISTINCT(self): + return self.getToken(HogQLParser.DISTINCT, 0) + def columnArgList(self): + return self.getTypedRuleContext(HogQLParser.ColumnArgListContext,0) + def accept(self, visitor:ParseTreeVisitor): if hasattr( visitor, "visitColumnExprWinFunctionTarget" ): @@ -5851,6 +5872,11 @@ def RPAREN(self, i:int=None): def columnExprList(self): return self.getTypedRuleContext(HogQLParser.ColumnExprListContext,0) + def DISTINCT(self): + return self.getToken(HogQLParser.DISTINCT, 0) + def columnArgList(self): + return self.getTypedRuleContext(HogQLParser.ColumnArgListContext,0) + def accept(self, visitor:ParseTreeVisitor): if hasattr( visitor, "visitColumnExprWinFunction" ): @@ -5943,9 +5969,9 @@ def columnExpr(self, _p:int=0): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 812 + self.state = 832 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,90,self._ctx) + la_ = self._interp.adaptivePredict(self._input,96,self._ctx) if la_ == 1: localctx = HogQLParser.ColumnExprCaseContext(self, localctx) self._ctx = localctx @@ -6115,13 +6141,39 @@ def columnExpr(self, _p:int=0): self.state = 742 self.match(HogQLParser.RPAREN) - self.state = 744 + self.state = 752 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==126: + self.state = 744 + self.match(HogQLParser.LPAREN) + self.state = 746 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,82,self._ctx) + if la_ == 1: + self.state = 745 + self.match(HogQLParser.DISTINCT) + + + self.state = 749 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): + self.state = 748 + self.columnArgList() + + + self.state = 751 + self.match(HogQLParser.RPAREN) + + + self.state = 754 self.match(HogQLParser.OVER) - self.state = 745 + self.state = 755 self.match(HogQLParser.LPAREN) - self.state = 746 + self.state = 756 self.windowExpr() - self.state = 747 + self.state = 757 self.match(HogQLParser.RPAREN) pass @@ -6129,24 +6181,50 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprWinFunctionTargetContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 749 + self.state = 759 self.identifier() - self.state = 750 + self.state = 760 self.match(HogQLParser.LPAREN) - self.state = 752 + self.state = 762 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): - self.state = 751 + self.state = 761 self.columnExprList() - self.state = 754 + self.state = 764 self.match(HogQLParser.RPAREN) - self.state = 756 + self.state = 774 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==126: + self.state = 766 + self.match(HogQLParser.LPAREN) + self.state = 768 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,86,self._ctx) + if la_ == 1: + self.state = 767 + self.match(HogQLParser.DISTINCT) + + + self.state = 771 + self._errHandler.sync(self) + _la = self._input.LA(1) + if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): + self.state = 770 + self.columnArgList() + + + self.state = 773 + self.match(HogQLParser.RPAREN) + + + self.state = 776 self.match(HogQLParser.OVER) - self.state = 757 + self.state = 777 self.identifier() pass @@ -6154,45 +6232,45 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprFunctionContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 759 + self.state = 779 self.identifier() - self.state = 765 + self.state = 785 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,84,self._ctx) + la_ = self._interp.adaptivePredict(self._input,90,self._ctx) if la_ == 1: - self.state = 760 + self.state = 780 self.match(HogQLParser.LPAREN) - self.state = 762 + self.state = 782 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): - self.state = 761 + self.state = 781 self.columnExprList() - self.state = 764 + self.state = 784 self.match(HogQLParser.RPAREN) - self.state = 767 + self.state = 787 self.match(HogQLParser.LPAREN) - self.state = 769 + self.state = 789 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,85,self._ctx) + la_ = self._interp.adaptivePredict(self._input,91,self._ctx) if la_ == 1: - self.state = 768 + self.state = 788 self.match(HogQLParser.DISTINCT) - self.state = 772 + self.state = 792 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): - self.state = 771 + self.state = 791 self.columnArgList() - self.state = 774 + self.state = 794 self.match(HogQLParser.RPAREN) pass @@ -6200,7 +6278,7 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprTagElementContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 776 + self.state = 796 self.hogqlxTagElement() pass @@ -6208,7 +6286,7 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprTemplateStringContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 777 + self.state = 797 self.templateString() pass @@ -6216,7 +6294,7 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprLiteralContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 778 + self.state = 798 self.literal() pass @@ -6224,9 +6302,9 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprNegateContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 779 + self.state = 799 self.match(HogQLParser.DASH) - self.state = 780 + self.state = 800 self.columnExpr(19) pass @@ -6234,9 +6312,9 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprNotContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 781 + self.state = 801 self.match(HogQLParser.NOT) - self.state = 782 + self.state = 802 self.columnExpr(13) pass @@ -6244,17 +6322,17 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprAsteriskContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 786 + self.state = 806 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -181272084561788930) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 201863462911) != 0): - self.state = 783 + self.state = 803 self.tableIdentifier() - self.state = 784 + self.state = 804 self.match(HogQLParser.DOT) - self.state = 788 + self.state = 808 self.match(HogQLParser.ASTERISK) pass @@ -6262,11 +6340,11 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprSubqueryContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 789 + self.state = 809 self.match(HogQLParser.LPAREN) - self.state = 790 + self.state = 810 self.selectUnionStmt() - self.state = 791 + self.state = 811 self.match(HogQLParser.RPAREN) pass @@ -6274,11 +6352,11 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprParensContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 793 + self.state = 813 self.match(HogQLParser.LPAREN) - self.state = 794 + self.state = 814 self.columnExpr(0) - self.state = 795 + self.state = 815 self.match(HogQLParser.RPAREN) pass @@ -6286,11 +6364,11 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprTupleContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 797 + self.state = 817 self.match(HogQLParser.LPAREN) - self.state = 798 + self.state = 818 self.columnExprList() - self.state = 799 + self.state = 819 self.match(HogQLParser.RPAREN) pass @@ -6298,17 +6376,17 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprArrayContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 801 + self.state = 821 self.match(HogQLParser.LBRACKET) - self.state = 803 + self.state = 823 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): - self.state = 802 + self.state = 822 self.columnExprList() - self.state = 805 + self.state = 825 self.match(HogQLParser.RBRACKET) pass @@ -6316,17 +6394,17 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprDictContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 806 + self.state = 826 self.match(HogQLParser.LBRACE) - self.state = 808 + self.state = 828 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): - self.state = 807 + self.state = 827 self.kvPairList() - self.state = 810 + self.state = 830 self.match(HogQLParser.RBRACE) pass @@ -6334,50 +6412,50 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprIdentifierContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 811 + self.state = 831 self.columnIdentifier() pass self._ctx.stop = self._input.LT(-1) - self.state = 907 + self.state = 927 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,101,self._ctx) + _alt = self._interp.adaptivePredict(self._input,107,self._ctx) while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: if _alt==1: if self._parseListeners is not None: self.triggerExitRuleEvent() _prevctx = localctx - self.state = 905 + self.state = 925 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,100,self._ctx) + la_ = self._interp.adaptivePredict(self._input,106,self._ctx) if la_ == 1: localctx = HogQLParser.ColumnExprPrecedence1Context(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) localctx.left = _prevctx self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 814 + self.state = 834 if not self.precpred(self._ctx, 18): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 18)") - self.state = 818 + self.state = 838 self._errHandler.sync(self) token = self._input.LA(1) if token in [108]: - self.state = 815 + self.state = 835 localctx.operator = self.match(HogQLParser.ASTERISK) pass elif token in [146]: - self.state = 816 + self.state = 836 localctx.operator = self.match(HogQLParser.SLASH) pass elif token in [133]: - self.state = 817 + self.state = 837 localctx.operator = self.match(HogQLParser.PERCENT) pass else: raise NoViableAltException(self) - self.state = 820 + self.state = 840 localctx.right = self.columnExpr(19) pass @@ -6385,29 +6463,29 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprPrecedence2Context(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) localctx.left = _prevctx self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 821 + self.state = 841 if not self.precpred(self._ctx, 17): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 17)") - self.state = 825 + self.state = 845 self._errHandler.sync(self) token = self._input.LA(1) if token in [134]: - self.state = 822 + self.state = 842 localctx.operator = self.match(HogQLParser.PLUS) pass elif token in [114]: - self.state = 823 + self.state = 843 localctx.operator = self.match(HogQLParser.DASH) pass elif token in [113]: - self.state = 824 + self.state = 844 localctx.operator = self.match(HogQLParser.CONCAT) pass else: raise NoViableAltException(self) - self.state = 827 + self.state = 847 localctx.right = self.columnExpr(18) pass @@ -6415,79 +6493,79 @@ def columnExpr(self, _p:int=0): localctx = HogQLParser.ColumnExprPrecedence3Context(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) localctx.left = _prevctx self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 828 + self.state = 848 if not self.precpred(self._ctx, 16): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 16)") - self.state = 853 + self.state = 873 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,96,self._ctx) + la_ = self._interp.adaptivePredict(self._input,102,self._ctx) if la_ == 1: - self.state = 829 + self.state = 849 localctx.operator = self.match(HogQLParser.EQ_DOUBLE) pass elif la_ == 2: - self.state = 830 + self.state = 850 localctx.operator = self.match(HogQLParser.EQ_SINGLE) pass elif la_ == 3: - self.state = 831 + self.state = 851 localctx.operator = self.match(HogQLParser.NOT_EQ) pass elif la_ == 4: - self.state = 832 + self.state = 852 localctx.operator = self.match(HogQLParser.LT_EQ) pass elif la_ == 5: - self.state = 833 + self.state = 853 localctx.operator = self.match(HogQLParser.LT) pass elif la_ == 6: - self.state = 834 + self.state = 854 localctx.operator = self.match(HogQLParser.GT_EQ) pass elif la_ == 7: - self.state = 835 + self.state = 855 localctx.operator = self.match(HogQLParser.GT) pass elif la_ == 8: - self.state = 837 + self.state = 857 self._errHandler.sync(self) _la = self._input.LA(1) if _la==56: - self.state = 836 + self.state = 856 localctx.operator = self.match(HogQLParser.NOT) - self.state = 839 + self.state = 859 self.match(HogQLParser.IN) - self.state = 841 + self.state = 861 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,94,self._ctx) + la_ = self._interp.adaptivePredict(self._input,100,self._ctx) if la_ == 1: - self.state = 840 + self.state = 860 self.match(HogQLParser.COHORT) pass elif la_ == 9: - self.state = 844 + self.state = 864 self._errHandler.sync(self) _la = self._input.LA(1) if _la==56: - self.state = 843 + self.state = 863 localctx.operator = self.match(HogQLParser.NOT) - self.state = 846 + self.state = 866 _la = self._input.LA(1) if not(_la==39 or _la==51): self._errHandler.recoverInline(self) @@ -6497,209 +6575,209 @@ def columnExpr(self, _p:int=0): pass elif la_ == 10: - self.state = 847 + self.state = 867 localctx.operator = self.match(HogQLParser.REGEX_SINGLE) pass elif la_ == 11: - self.state = 848 + self.state = 868 localctx.operator = self.match(HogQLParser.REGEX_DOUBLE) pass elif la_ == 12: - self.state = 849 + self.state = 869 localctx.operator = self.match(HogQLParser.NOT_REGEX) pass elif la_ == 13: - self.state = 850 + self.state = 870 localctx.operator = self.match(HogQLParser.IREGEX_SINGLE) pass elif la_ == 14: - self.state = 851 + self.state = 871 localctx.operator = self.match(HogQLParser.IREGEX_DOUBLE) pass elif la_ == 15: - self.state = 852 + self.state = 872 localctx.operator = self.match(HogQLParser.NOT_IREGEX) pass - self.state = 855 + self.state = 875 localctx.right = self.columnExpr(17) pass elif la_ == 4: localctx = HogQLParser.ColumnExprNullishContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 856 + self.state = 876 if not self.precpred(self._ctx, 14): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 14)") - self.state = 857 + self.state = 877 self.match(HogQLParser.NULLISH) - self.state = 858 + self.state = 878 self.columnExpr(15) pass elif la_ == 5: localctx = HogQLParser.ColumnExprAndContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 859 + self.state = 879 if not self.precpred(self._ctx, 12): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 12)") - self.state = 860 + self.state = 880 self.match(HogQLParser.AND) - self.state = 861 + self.state = 881 self.columnExpr(13) pass elif la_ == 6: localctx = HogQLParser.ColumnExprOrContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 862 + self.state = 882 if not self.precpred(self._ctx, 11): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 11)") - self.state = 863 + self.state = 883 self.match(HogQLParser.OR) - self.state = 864 + self.state = 884 self.columnExpr(12) pass elif la_ == 7: localctx = HogQLParser.ColumnExprBetweenContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 865 + self.state = 885 if not self.precpred(self._ctx, 10): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 10)") - self.state = 867 + self.state = 887 self._errHandler.sync(self) _la = self._input.LA(1) if _la==56: - self.state = 866 + self.state = 886 self.match(HogQLParser.NOT) - self.state = 869 + self.state = 889 self.match(HogQLParser.BETWEEN) - self.state = 870 + self.state = 890 self.columnExpr(0) - self.state = 871 + self.state = 891 self.match(HogQLParser.AND) - self.state = 872 + self.state = 892 self.columnExpr(11) pass elif la_ == 8: localctx = HogQLParser.ColumnExprTernaryOpContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 874 + self.state = 894 if not self.precpred(self._ctx, 9): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 9)") - self.state = 875 + self.state = 895 self.match(HogQLParser.QUERY) - self.state = 876 + self.state = 896 self.columnExpr(0) - self.state = 877 + self.state = 897 self.match(HogQLParser.COLON) - self.state = 878 + self.state = 898 self.columnExpr(9) pass elif la_ == 9: localctx = HogQLParser.ColumnExprArrayAccessContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 880 + self.state = 900 if not self.precpred(self._ctx, 22): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 22)") - self.state = 881 + self.state = 901 self.match(HogQLParser.LBRACKET) - self.state = 882 + self.state = 902 self.columnExpr(0) - self.state = 883 + self.state = 903 self.match(HogQLParser.RBRACKET) pass elif la_ == 10: localctx = HogQLParser.ColumnExprTupleAccessContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 885 + self.state = 905 if not self.precpred(self._ctx, 21): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 21)") - self.state = 886 + self.state = 906 self.match(HogQLParser.DOT) - self.state = 887 + self.state = 907 self.match(HogQLParser.DECIMAL_LITERAL) pass elif la_ == 11: localctx = HogQLParser.ColumnExprPropertyAccessContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 888 + self.state = 908 if not self.precpred(self._ctx, 20): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 20)") - self.state = 889 + self.state = 909 self.match(HogQLParser.DOT) - self.state = 890 + self.state = 910 self.identifier() pass elif la_ == 12: localctx = HogQLParser.ColumnExprIsNullContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 891 + self.state = 911 if not self.precpred(self._ctx, 15): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 15)") - self.state = 892 + self.state = 912 self.match(HogQLParser.IS) - self.state = 894 + self.state = 914 self._errHandler.sync(self) _la = self._input.LA(1) if _la==56: - self.state = 893 + self.state = 913 self.match(HogQLParser.NOT) - self.state = 896 + self.state = 916 self.match(HogQLParser.NULL_SQL) pass elif la_ == 13: localctx = HogQLParser.ColumnExprAliasContext(self, HogQLParser.ColumnExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_columnExpr) - self.state = 897 + self.state = 917 if not self.precpred(self._ctx, 8): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 8)") - self.state = 903 + self.state = 923 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,99,self._ctx) + la_ = self._interp.adaptivePredict(self._input,105,self._ctx) if la_ == 1: - self.state = 898 + self.state = 918 self.alias() pass elif la_ == 2: - self.state = 899 + self.state = 919 self.match(HogQLParser.AS) - self.state = 900 + self.state = 920 self.identifier() pass elif la_ == 3: - self.state = 901 + self.state = 921 self.match(HogQLParser.AS) - self.state = 902 + self.state = 922 self.match(HogQLParser.STRING_LITERAL) pass @@ -6707,9 +6785,9 @@ def columnExpr(self, _p:int=0): pass - self.state = 909 + self.state = 929 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,101,self._ctx) + _alt = self._interp.adaptivePredict(self._input,107,self._ctx) except RecognitionException as re: localctx.exception = re @@ -6759,17 +6837,17 @@ def columnArgList(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 910 + self.state = 930 self.columnArgExpr() - self.state = 915 + self.state = 935 self._errHandler.sync(self) _la = self._input.LA(1) while _la==112: - self.state = 911 + self.state = 931 self.match(HogQLParser.COMMA) - self.state = 912 + self.state = 932 self.columnArgExpr() - self.state = 917 + self.state = 937 self._errHandler.sync(self) _la = self._input.LA(1) @@ -6814,18 +6892,18 @@ def columnArgExpr(self): localctx = HogQLParser.ColumnArgExprContext(self, self._ctx, self.state) self.enterRule(localctx, 110, self.RULE_columnArgExpr) try: - self.state = 920 + self.state = 940 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,103,self._ctx) + la_ = self._interp.adaptivePredict(self._input,109,self._ctx) if la_ == 1: self.enterOuterAlt(localctx, 1) - self.state = 918 + self.state = 938 self.columnLambdaExpr() pass elif la_ == 2: self.enterOuterAlt(localctx, 2) - self.state = 919 + self.state = 939 self.columnExpr(0) pass @@ -6891,41 +6969,41 @@ def columnLambdaExpr(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 941 + self.state = 961 self._errHandler.sync(self) token = self._input.LA(1) if token in [126]: - self.state = 922 + self.state = 942 self.match(HogQLParser.LPAREN) - self.state = 923 + self.state = 943 self.identifier() - self.state = 928 + self.state = 948 self._errHandler.sync(self) _la = self._input.LA(1) while _la==112: - self.state = 924 + self.state = 944 self.match(HogQLParser.COMMA) - self.state = 925 + self.state = 945 self.identifier() - self.state = 930 + self.state = 950 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 931 + self.state = 951 self.match(HogQLParser.RPAREN) pass elif token in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 56, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 97, 98, 99, 101]: - self.state = 933 + self.state = 953 self.identifier() - self.state = 938 + self.state = 958 self._errHandler.sync(self) _la = self._input.LA(1) while _la==112: - self.state = 934 + self.state = 954 self.match(HogQLParser.COMMA) - self.state = 935 + self.state = 955 self.identifier() - self.state = 940 + self.state = 960 self._errHandler.sync(self) _la = self._input.LA(1) @@ -6933,9 +7011,9 @@ def columnLambdaExpr(self): else: raise NoViableAltException(self) - self.state = 943 + self.state = 963 self.match(HogQLParser.ARROW) - self.state = 944 + self.state = 964 self.columnExpr(0) except RecognitionException as re: localctx.exception = re @@ -7040,66 +7118,66 @@ def hogqlxTagElement(self): self.enterRule(localctx, 114, self.RULE_hogqlxTagElement) self._la = 0 # Token type try: - self.state = 974 + self.state = 994 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,110,self._ctx) + la_ = self._interp.adaptivePredict(self._input,116,self._ctx) if la_ == 1: localctx = HogQLParser.HogqlxTagElementClosedContext(self, localctx) self.enterOuterAlt(localctx, 1) - self.state = 946 + self.state = 966 self.match(HogQLParser.LT) - self.state = 947 + self.state = 967 self.identifier() - self.state = 951 + self.state = 971 self._errHandler.sync(self) _la = self._input.LA(1) while (((_la) & ~0x3f) == 0 and ((1 << _la) & -181272084561788930) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 201863462911) != 0): - self.state = 948 + self.state = 968 self.hogqlxTagAttribute() - self.state = 953 + self.state = 973 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 954 + self.state = 974 self.match(HogQLParser.SLASH) - self.state = 955 + self.state = 975 self.match(HogQLParser.GT) pass elif la_ == 2: localctx = HogQLParser.HogqlxTagElementNestedContext(self, localctx) self.enterOuterAlt(localctx, 2) - self.state = 957 + self.state = 977 self.match(HogQLParser.LT) - self.state = 958 + self.state = 978 self.identifier() - self.state = 962 + self.state = 982 self._errHandler.sync(self) _la = self._input.LA(1) while (((_la) & ~0x3f) == 0 and ((1 << _la) & -181272084561788930) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 201863462911) != 0): - self.state = 959 + self.state = 979 self.hogqlxTagAttribute() - self.state = 964 + self.state = 984 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 965 + self.state = 985 self.match(HogQLParser.GT) - self.state = 967 + self.state = 987 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,109,self._ctx) + la_ = self._interp.adaptivePredict(self._input,115,self._ctx) if la_ == 1: - self.state = 966 + self.state = 986 self.hogqlxTagElement() - self.state = 969 + self.state = 989 self.match(HogQLParser.LT) - self.state = 970 + self.state = 990 self.match(HogQLParser.SLASH) - self.state = 971 + self.state = 991 self.identifier() - self.state = 972 + self.state = 992 self.match(HogQLParser.GT) pass @@ -7158,36 +7236,36 @@ def hogqlxTagAttribute(self): localctx = HogQLParser.HogqlxTagAttributeContext(self, self._ctx, self.state) self.enterRule(localctx, 116, self.RULE_hogqlxTagAttribute) try: - self.state = 987 + self.state = 1007 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,111,self._ctx) + la_ = self._interp.adaptivePredict(self._input,117,self._ctx) if la_ == 1: self.enterOuterAlt(localctx, 1) - self.state = 976 + self.state = 996 self.identifier() - self.state = 977 + self.state = 997 self.match(HogQLParser.EQ_SINGLE) - self.state = 978 + self.state = 998 self.string() pass elif la_ == 2: self.enterOuterAlt(localctx, 2) - self.state = 980 + self.state = 1000 self.identifier() - self.state = 981 + self.state = 1001 self.match(HogQLParser.EQ_SINGLE) - self.state = 982 + self.state = 1002 self.match(HogQLParser.LBRACE) - self.state = 983 + self.state = 1003 self.columnExpr(0) - self.state = 984 + self.state = 1004 self.match(HogQLParser.RBRACE) pass elif la_ == 3: self.enterOuterAlt(localctx, 3) - self.state = 986 + self.state = 1006 self.identifier() pass @@ -7240,17 +7318,17 @@ def withExprList(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 989 + self.state = 1009 self.withExpr() - self.state = 994 + self.state = 1014 self._errHandler.sync(self) _la = self._input.LA(1) while _la==112: - self.state = 990 + self.state = 1010 self.match(HogQLParser.COMMA) - self.state = 991 + self.state = 1011 self.withExpr() - self.state = 996 + self.state = 1016 self._errHandler.sync(self) _la = self._input.LA(1) @@ -7334,32 +7412,32 @@ def withExpr(self): localctx = HogQLParser.WithExprContext(self, self._ctx, self.state) self.enterRule(localctx, 120, self.RULE_withExpr) try: - self.state = 1007 + self.state = 1027 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,113,self._ctx) + la_ = self._interp.adaptivePredict(self._input,119,self._ctx) if la_ == 1: localctx = HogQLParser.WithExprSubqueryContext(self, localctx) self.enterOuterAlt(localctx, 1) - self.state = 997 + self.state = 1017 self.identifier() - self.state = 998 + self.state = 1018 self.match(HogQLParser.AS) - self.state = 999 + self.state = 1019 self.match(HogQLParser.LPAREN) - self.state = 1000 + self.state = 1020 self.selectUnionStmt() - self.state = 1001 + self.state = 1021 self.match(HogQLParser.RPAREN) pass elif la_ == 2: localctx = HogQLParser.WithExprColumnContext(self, localctx) self.enterOuterAlt(localctx, 2) - self.state = 1003 + self.state = 1023 self.columnExpr(0) - self.state = 1004 + self.state = 1024 self.match(HogQLParser.AS) - self.state = 1005 + self.state = 1025 self.identifier() pass @@ -7412,27 +7490,27 @@ def columnIdentifier(self): localctx = HogQLParser.ColumnIdentifierContext(self, self._ctx, self.state) self.enterRule(localctx, 122, self.RULE_columnIdentifier) try: - self.state = 1016 + self.state = 1036 self._errHandler.sync(self) token = self._input.LA(1) if token in [124]: self.enterOuterAlt(localctx, 1) - self.state = 1009 + self.state = 1029 self.placeholder() pass elif token in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 56, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 97, 98, 99, 101]: self.enterOuterAlt(localctx, 2) - self.state = 1013 + self.state = 1033 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,114,self._ctx) + la_ = self._interp.adaptivePredict(self._input,120,self._ctx) if la_ == 1: - self.state = 1010 + self.state = 1030 self.tableIdentifier() - self.state = 1011 + self.state = 1031 self.match(HogQLParser.DOT) - self.state = 1015 + self.state = 1035 self.nestedIdentifier() pass else: @@ -7485,20 +7563,20 @@ def nestedIdentifier(self): self.enterRule(localctx, 124, self.RULE_nestedIdentifier) try: self.enterOuterAlt(localctx, 1) - self.state = 1018 + self.state = 1038 self.identifier() - self.state = 1023 + self.state = 1043 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,116,self._ctx) + _alt = self._interp.adaptivePredict(self._input,122,self._ctx) while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: if _alt==1: - self.state = 1019 + self.state = 1039 self.match(HogQLParser.DOT) - self.state = 1020 + self.state = 1040 self.identifier() - self.state = 1025 + self.state = 1045 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,116,self._ctx) + _alt = self._interp.adaptivePredict(self._input,122,self._ctx) except RecognitionException as re: localctx.exception = re @@ -7649,15 +7727,15 @@ def tableExpr(self, _p:int=0): self.enterRecursionRule(localctx, 126, self.RULE_tableExpr, _p) try: self.enterOuterAlt(localctx, 1) - self.state = 1035 + self.state = 1055 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,117,self._ctx) + la_ = self._interp.adaptivePredict(self._input,123,self._ctx) if la_ == 1: localctx = HogQLParser.TableExprIdentifierContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 1027 + self.state = 1047 self.tableIdentifier() pass @@ -7665,7 +7743,7 @@ def tableExpr(self, _p:int=0): localctx = HogQLParser.TableExprFunctionContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 1028 + self.state = 1048 self.tableFunctionExpr() pass @@ -7673,11 +7751,11 @@ def tableExpr(self, _p:int=0): localctx = HogQLParser.TableExprSubqueryContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 1029 + self.state = 1049 self.match(HogQLParser.LPAREN) - self.state = 1030 + self.state = 1050 self.selectUnionStmt() - self.state = 1031 + self.state = 1051 self.match(HogQLParser.RPAREN) pass @@ -7685,7 +7763,7 @@ def tableExpr(self, _p:int=0): localctx = HogQLParser.TableExprTagContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 1033 + self.state = 1053 self.hogqlxTagElement() pass @@ -7693,15 +7771,15 @@ def tableExpr(self, _p:int=0): localctx = HogQLParser.TableExprPlaceholderContext(self, localctx) self._ctx = localctx _prevctx = localctx - self.state = 1034 + self.state = 1054 self.placeholder() pass self._ctx.stop = self._input.LT(-1) - self.state = 1045 + self.state = 1065 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,119,self._ctx) + _alt = self._interp.adaptivePredict(self._input,125,self._ctx) while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: if _alt==1: if self._parseListeners is not None: @@ -7709,29 +7787,29 @@ def tableExpr(self, _p:int=0): _prevctx = localctx localctx = HogQLParser.TableExprAliasContext(self, HogQLParser.TableExprContext(self, _parentctx, _parentState)) self.pushNewRecursionContext(localctx, _startState, self.RULE_tableExpr) - self.state = 1037 + self.state = 1057 if not self.precpred(self._ctx, 3): from antlr4.error.Errors import FailedPredicateException raise FailedPredicateException(self, "self.precpred(self._ctx, 3)") - self.state = 1041 + self.state = 1061 self._errHandler.sync(self) token = self._input.LA(1) if token in [19, 28, 37, 46, 101]: - self.state = 1038 + self.state = 1058 self.alias() pass elif token in [6]: - self.state = 1039 + self.state = 1059 self.match(HogQLParser.AS) - self.state = 1040 + self.state = 1060 self.identifier() pass else: raise NoViableAltException(self) - self.state = 1047 + self.state = 1067 self._errHandler.sync(self) - _alt = self._interp.adaptivePredict(self._input,119,self._ctx) + _alt = self._interp.adaptivePredict(self._input,125,self._ctx) except RecognitionException as re: localctx.exception = re @@ -7782,19 +7860,19 @@ def tableFunctionExpr(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1048 + self.state = 1068 self.identifier() - self.state = 1049 + self.state = 1069 self.match(HogQLParser.LPAREN) - self.state = 1051 + self.state = 1071 self._errHandler.sync(self) _la = self._input.LA(1) if (((_la) & ~0x3f) == 0 and ((1 << _la) & -1125900443713538) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 8076106347046764543) != 0) or ((((_la - 128)) & ~0x3f) == 0 and ((1 << (_la - 128)) & 577) != 0): - self.state = 1050 + self.state = 1070 self.tableArgList() - self.state = 1053 + self.state = 1073 self.match(HogQLParser.RPAREN) except RecognitionException as re: localctx.exception = re @@ -7841,17 +7919,17 @@ def tableIdentifier(self): self.enterRule(localctx, 130, self.RULE_tableIdentifier) try: self.enterOuterAlt(localctx, 1) - self.state = 1058 + self.state = 1078 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,121,self._ctx) + la_ = self._interp.adaptivePredict(self._input,127,self._ctx) if la_ == 1: - self.state = 1055 + self.state = 1075 self.databaseIdentifier() - self.state = 1056 + self.state = 1076 self.match(HogQLParser.DOT) - self.state = 1060 + self.state = 1080 self.identifier() except RecognitionException as re: localctx.exception = re @@ -7901,17 +7979,17 @@ def tableArgList(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1062 + self.state = 1082 self.columnExpr(0) - self.state = 1067 + self.state = 1087 self._errHandler.sync(self) _la = self._input.LA(1) while _la==112: - self.state = 1063 + self.state = 1083 self.match(HogQLParser.COMMA) - self.state = 1064 + self.state = 1084 self.columnExpr(0) - self.state = 1069 + self.state = 1089 self._errHandler.sync(self) _la = self._input.LA(1) @@ -7953,7 +8031,7 @@ def databaseIdentifier(self): self.enterRule(localctx, 134, self.RULE_databaseIdentifier) try: self.enterOuterAlt(localctx, 1) - self.state = 1070 + self.state = 1090 self.identifier() except RecognitionException as re: localctx.exception = re @@ -8004,19 +8082,19 @@ def floatingLiteral(self): self.enterRule(localctx, 136, self.RULE_floatingLiteral) self._la = 0 # Token type try: - self.state = 1080 + self.state = 1100 self._errHandler.sync(self) token = self._input.LA(1) if token in [102]: self.enterOuterAlt(localctx, 1) - self.state = 1072 + self.state = 1092 self.match(HogQLParser.FLOATING_LITERAL) pass elif token in [116]: self.enterOuterAlt(localctx, 2) - self.state = 1073 + self.state = 1093 self.match(HogQLParser.DOT) - self.state = 1074 + self.state = 1094 _la = self._input.LA(1) if not(_la==103 or _la==104): self._errHandler.recoverInline(self) @@ -8026,15 +8104,15 @@ def floatingLiteral(self): pass elif token in [104]: self.enterOuterAlt(localctx, 3) - self.state = 1075 + self.state = 1095 self.match(HogQLParser.DECIMAL_LITERAL) - self.state = 1076 + self.state = 1096 self.match(HogQLParser.DOT) - self.state = 1078 + self.state = 1098 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,123,self._ctx) + la_ = self._interp.adaptivePredict(self._input,129,self._ctx) if la_ == 1: - self.state = 1077 + self.state = 1097 _la = self._input.LA(1) if not(_la==103 or _la==104): self._errHandler.recoverInline(self) @@ -8107,11 +8185,11 @@ def numberLiteral(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1083 + self.state = 1103 self._errHandler.sync(self) _la = self._input.LA(1) if _la==114 or _la==134: - self.state = 1082 + self.state = 1102 _la = self._input.LA(1) if not(_la==114 or _la==134): self._errHandler.recoverInline(self) @@ -8120,36 +8198,36 @@ def numberLiteral(self): self.consume() - self.state = 1091 + self.state = 1111 self._errHandler.sync(self) - la_ = self._interp.adaptivePredict(self._input,126,self._ctx) + la_ = self._interp.adaptivePredict(self._input,132,self._ctx) if la_ == 1: - self.state = 1085 + self.state = 1105 self.floatingLiteral() pass elif la_ == 2: - self.state = 1086 + self.state = 1106 self.match(HogQLParser.OCTAL_LITERAL) pass elif la_ == 3: - self.state = 1087 + self.state = 1107 self.match(HogQLParser.DECIMAL_LITERAL) pass elif la_ == 4: - self.state = 1088 + self.state = 1108 self.match(HogQLParser.HEXADECIMAL_LITERAL) pass elif la_ == 5: - self.state = 1089 + self.state = 1109 self.match(HogQLParser.INF) pass elif la_ == 6: - self.state = 1090 + self.state = 1110 self.match(HogQLParser.NAN_SQL) pass @@ -8197,22 +8275,22 @@ def literal(self): localctx = HogQLParser.LiteralContext(self, self._ctx, self.state) self.enterRule(localctx, 140, self.RULE_literal) try: - self.state = 1096 + self.state = 1116 self._errHandler.sync(self) token = self._input.LA(1) if token in [41, 55, 102, 103, 104, 105, 114, 116, 134]: self.enterOuterAlt(localctx, 1) - self.state = 1093 + self.state = 1113 self.numberLiteral() pass elif token in [106]: self.enterOuterAlt(localctx, 2) - self.state = 1094 + self.state = 1114 self.match(HogQLParser.STRING_LITERAL) pass elif token in [57]: self.enterOuterAlt(localctx, 3) - self.state = 1095 + self.state = 1115 self.match(HogQLParser.NULL_SQL) pass else: @@ -8277,7 +8355,7 @@ def interval(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1098 + self.state = 1118 _la = self._input.LA(1) if not((((_la) & ~0x3f) == 0 and ((1 << _la) & 27021666484748288) != 0) or ((((_la - 68)) & ~0x3f) == 0 and ((1 << (_la - 68)) & 2181038337) != 0)): self._errHandler.recoverInline(self) @@ -8574,7 +8652,7 @@ def keyword(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1100 + self.state = 1120 _la = self._input.LA(1) if not((((_la) & ~0x3f) == 0 and ((1 << _la) & -208293751046537218) != 0) or ((((_la - 64)) & ~0x3f) == 0 and ((1 << (_la - 64)) & 29527896047) != 0)): self._errHandler.recoverInline(self) @@ -8628,7 +8706,7 @@ def keywordForAlias(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1102 + self.state = 1122 _la = self._input.LA(1) if not((((_la) & ~0x3f) == 0 and ((1 << _la) & 70506452090880) != 0)): self._errHandler.recoverInline(self) @@ -8675,17 +8753,17 @@ def alias(self): localctx = HogQLParser.AliasContext(self, self._ctx, self.state) self.enterRule(localctx, 148, self.RULE_alias) try: - self.state = 1106 + self.state = 1126 self._errHandler.sync(self) token = self._input.LA(1) if token in [101]: self.enterOuterAlt(localctx, 1) - self.state = 1104 + self.state = 1124 self.match(HogQLParser.IDENTIFIER) pass elif token in [19, 28, 37, 46]: self.enterOuterAlt(localctx, 2) - self.state = 1105 + self.state = 1125 self.keywordForAlias() pass else: @@ -8735,22 +8813,22 @@ def identifier(self): localctx = HogQLParser.IdentifierContext(self, self._ctx, self.state) self.enterRule(localctx, 150, self.RULE_identifier) try: - self.state = 1111 + self.state = 1131 self._errHandler.sync(self) token = self._input.LA(1) if token in [101]: self.enterOuterAlt(localctx, 1) - self.state = 1108 + self.state = 1128 self.match(HogQLParser.IDENTIFIER) pass elif token in [20, 36, 53, 54, 68, 76, 93, 99]: self.enterOuterAlt(localctx, 2) - self.state = 1109 + self.state = 1129 self.interval() pass elif token in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 35, 37, 38, 39, 40, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 56, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 70, 71, 72, 73, 74, 75, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 94, 95, 97, 98]: self.enterOuterAlt(localctx, 3) - self.state = 1110 + self.state = 1130 self.keyword() pass else: @@ -8801,11 +8879,11 @@ def enumValue(self): self.enterRule(localctx, 152, self.RULE_enumValue) try: self.enterOuterAlt(localctx, 1) - self.state = 1113 + self.state = 1133 self.string() - self.state = 1114 + self.state = 1134 self.match(HogQLParser.EQ_SINGLE) - self.state = 1115 + self.state = 1135 self.numberLiteral() except RecognitionException as re: localctx.exception = re @@ -8851,11 +8929,11 @@ def placeholder(self): self.enterRule(localctx, 154, self.RULE_placeholder) try: self.enterOuterAlt(localctx, 1) - self.state = 1117 + self.state = 1137 self.match(HogQLParser.LBRACE) - self.state = 1118 + self.state = 1138 self.identifier() - self.state = 1119 + self.state = 1139 self.match(HogQLParser.RBRACE) except RecognitionException as re: localctx.exception = re @@ -8897,17 +8975,17 @@ def string(self): localctx = HogQLParser.StringContext(self, self._ctx, self.state) self.enterRule(localctx, 156, self.RULE_string) try: - self.state = 1123 + self.state = 1143 self._errHandler.sync(self) token = self._input.LA(1) if token in [106]: self.enterOuterAlt(localctx, 1) - self.state = 1121 + self.state = 1141 self.match(HogQLParser.STRING_LITERAL) pass elif token in [137]: self.enterOuterAlt(localctx, 2) - self.state = 1122 + self.state = 1142 self.templateString() pass else: @@ -8961,19 +9039,19 @@ def templateString(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1125 + self.state = 1145 self.match(HogQLParser.QUOTE_SINGLE_TEMPLATE) - self.state = 1129 + self.state = 1149 self._errHandler.sync(self) _la = self._input.LA(1) while _la==151 or _la==152: - self.state = 1126 + self.state = 1146 self.stringContents() - self.state = 1131 + self.state = 1151 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 1132 + self.state = 1152 self.match(HogQLParser.QUOTE_SINGLE) except RecognitionException as re: localctx.exception = re @@ -9021,21 +9099,21 @@ def stringContents(self): localctx = HogQLParser.StringContentsContext(self, self._ctx, self.state) self.enterRule(localctx, 160, self.RULE_stringContents) try: - self.state = 1139 + self.state = 1159 self._errHandler.sync(self) token = self._input.LA(1) if token in [152]: self.enterOuterAlt(localctx, 1) - self.state = 1134 + self.state = 1154 self.match(HogQLParser.STRING_ESCAPE_TRIGGER) - self.state = 1135 + self.state = 1155 self.columnExpr(0) - self.state = 1136 + self.state = 1156 self.match(HogQLParser.RBRACE) pass elif token in [151]: self.enterOuterAlt(localctx, 2) - self.state = 1138 + self.state = 1158 self.match(HogQLParser.STRING_TEXT) pass else: @@ -9089,19 +9167,19 @@ def fullTemplateString(self): self._la = 0 # Token type try: self.enterOuterAlt(localctx, 1) - self.state = 1141 + self.state = 1161 self.match(HogQLParser.QUOTE_SINGLE_TEMPLATE_FULL) - self.state = 1145 + self.state = 1165 self._errHandler.sync(self) _la = self._input.LA(1) while _la==153 or _la==154: - self.state = 1142 + self.state = 1162 self.stringContentsFull() - self.state = 1147 + self.state = 1167 self._errHandler.sync(self) _la = self._input.LA(1) - self.state = 1148 + self.state = 1168 self.match(HogQLParser.EOF) except RecognitionException as re: localctx.exception = re @@ -9149,21 +9227,21 @@ def stringContentsFull(self): localctx = HogQLParser.StringContentsFullContext(self, self._ctx, self.state) self.enterRule(localctx, 164, self.RULE_stringContentsFull) try: - self.state = 1155 + self.state = 1175 self._errHandler.sync(self) token = self._input.LA(1) if token in [154]: self.enterOuterAlt(localctx, 1) - self.state = 1150 + self.state = 1170 self.match(HogQLParser.FULL_STRING_ESCAPE_TRIGGER) - self.state = 1151 + self.state = 1171 self.columnExpr(0) - self.state = 1152 + self.state = 1172 self.match(HogQLParser.RBRACE) pass elif token in [153]: self.enterOuterAlt(localctx, 2) - self.state = 1154 + self.state = 1174 self.match(HogQLParser.FULL_STRING_TEXT) pass else: diff --git a/posthog/hogql/parser.py b/posthog/hogql/parser.py index bab0e9486d0034..bf08f5c122635a 100644 --- a/posthog/hogql/parser.py +++ b/posthog/hogql/parser.py @@ -822,14 +822,16 @@ def visitColumnExprNot(self, ctx: HogQLParser.ColumnExprNotContext): def visitColumnExprWinFunctionTarget(self, ctx: HogQLParser.ColumnExprWinFunctionTargetContext): return ast.WindowFunction( name=self.visit(ctx.identifier(0)), - args=self.visit(ctx.columnExprList()) if ctx.columnExprList() else [], + exprs=self.visit(ctx.columnExprList()) if ctx.columnExprList() else [], + args=self.visit(ctx.columnArgList()) if ctx.columnArgList() else [], over_identifier=self.visit(ctx.identifier(1)), ) def visitColumnExprWinFunction(self, ctx: HogQLParser.ColumnExprWinFunctionContext): return ast.WindowFunction( name=self.visit(ctx.identifier()), - args=self.visit(ctx.columnExprList()) if ctx.columnExprList() else [], + exprs=self.visit(ctx.columnExprList()) if ctx.columnExprList() else [], + args=self.visit(ctx.columnArgList()) if ctx.columnArgList() else [], over_expr=self.visit(ctx.windowExpr()) if ctx.windowExpr() else None, ) diff --git a/posthog/hogql/printer.py b/posthog/hogql/printer.py index e8ea399e724427..3104d121112de2 100644 --- a/posthog/hogql/printer.py +++ b/posthog/hogql/printer.py @@ -1140,8 +1140,11 @@ def visit_window_expr(self, node: ast.WindowExpr): return " ".join(strings) def visit_window_function(self, node: ast.WindowFunction): + identifier = self._print_identifier(node.name) + exprs = ", ".join(self.visit(expr) for expr in node.exprs or []) + args = "(" + (", ".join(self.visit(arg) for arg in node.args or [])) + ")" if node.args else "" over = f"({self.visit(node.over_expr)})" if node.over_expr else self._print_identifier(node.over_identifier) - return f"{self._print_identifier(node.name)}({', '.join(self.visit(expr) for expr in node.args or [])}) OVER {over}" + return f"{identifier}({exprs}){args} OVER {over}" def visit_window_frame_expr(self, node: ast.WindowFrameExpr): if node.frame_type == "PRECEDING": diff --git a/posthog/hogql/test/_test_parser.py b/posthog/hogql/test/_test_parser.py index 4a76240ffdc90a..d1bff88ebfcffe 100644 --- a/posthog/hogql/test/_test_parser.py +++ b/posthog/hogql/test/_test_parser.py @@ -1409,7 +1409,7 @@ def test_window_functions(self): alias="timestamp", expr=ast.WindowFunction( name="min", - args=[ast.Field(chain=["timestamp"])], + exprs=[ast.Field(chain=["timestamp"])], over_expr=ast.WindowExpr( partition_by=[ast.Field(chain=["person", "id"])], order_by=[ @@ -1429,6 +1429,32 @@ def test_window_functions(self): ) self.assertEqual(expr, expected) + def test_window_functions_call_arg(self): + query = "SELECT quantiles(0.0, 0.25, 0.5, 0.75, 1.0)(distinct distinct_id) over () as values FROM events" + expr = self._select(query) + expected = ast.SelectQuery( + select=[ + ast.Alias( + alias="values", + expr=ast.WindowFunction( + name="quantiles", + args=[ast.Field(chain=["distinct_id"])], + exprs=[ + ast.Constant(value=0.0), + ast.Constant(value=0.25), + ast.Constant(value=0.5), + ast.Constant(value=0.75), + ast.Constant(value=1.0), + ], + over_expr=ast.WindowExpr(), + ), + hidden=False, + ) + ], + select_from=ast.JoinExpr(table=ast.Field(chain=["events"])), + ) + self.assertEqual(expr, expected) + def test_window_functions_with_window(self): query = "SELECT person.id, min(timestamp) over win1 AS timestamp FROM events WINDOW win1 as (PARTITION by person.id ORDER BY timestamp DESC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)" expr = self._select(query) @@ -1439,7 +1465,7 @@ def test_window_functions_with_window(self): alias="timestamp", expr=ast.WindowFunction( name="min", - args=[ast.Field(chain=["timestamp"])], + exprs=[ast.Field(chain=["timestamp"])], over_identifier="win1", ), ), diff --git a/posthog/hogql/test/test_printer.py b/posthog/hogql/test/test_printer.py index 52807f90d200c3..5bf05573e671f8 100644 --- a/posthog/hogql/test/test_printer.py +++ b/posthog/hogql/test/test_printer.py @@ -925,6 +925,14 @@ def test_window_functions_with_window(self): f"SELECT events.distinct_id AS distinct_id, min(toTimeZone(events.timestamp, %(hogql_val_0)s)) OVER win1 AS timestamp FROM events WHERE equals(events.team_id, {self.team.pk}) WINDOW win1 AS (PARTITION BY events.distinct_id ORDER BY timestamp DESC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) LIMIT {MAX_SELECT_RETURNED_ROWS}", ) + def test_window_functions_with_arg(self): + self.assertEqual( + self._select( + "SELECT quantiles(0.0, 0.25, 0.5, 0.75, 1.0)(distinct distinct_id) over () as values FROM events" + ), + f"SELECT quantiles(0.0, 0.25, 0.5, 0.75, 1.0)(events.distinct_id) OVER () AS values FROM events WHERE equals(events.team_id, {self.team.pk}) LIMIT 50000", + ) + def test_nullish_concat(self): self.assertEqual( self._expr("concat(null, 'a', 3, toString(4), toString(NULL))"), diff --git a/posthog/hogql/visitor.py b/posthog/hogql/visitor.py index 84b34bddbf8c4f..f4d8c6f308c20d 100644 --- a/posthog/hogql/visitor.py +++ b/posthog/hogql/visitor.py @@ -246,8 +246,10 @@ def visit_window_expr(self, node: ast.WindowExpr): self.visit(node.frame_end) def visit_window_function(self, node: ast.WindowFunction): - for expr in node.args or []: + for expr in node.exprs or []: self.visit(expr) + for arg in node.args or []: + self.visit(arg) self.visit(node.over_expr) def visit_window_frame_expr(self, node: ast.WindowFrameExpr): @@ -553,7 +555,8 @@ def visit_window_function(self, node: ast.WindowFunction): end=None if self.clear_locations else node.end, type=None if self.clear_types else node.type, name=node.name, - args=[self.visit(expr) for expr in node.args] if node.args else None, + exprs=[self.visit(expr) for expr in node.exprs] if node.exprs else None, + args=[self.visit(arg) for arg in node.args] if node.args else None, over_expr=self.visit(node.over_expr) if node.over_expr else None, over_identifier=node.over_identifier, ) diff --git a/posthog/hogql_queries/insights/trends/display.py b/posthog/hogql_queries/insights/trends/display.py index f0d2cb2d125be3..4a1229e7ba9c25 100644 --- a/posthog/hogql_queries/insights/trends/display.py +++ b/posthog/hogql_queries/insights/trends/display.py @@ -81,7 +81,7 @@ def _get_cumulative_query(self, inner_query: ast.SelectQuery, breakdown_enabled: alias="count", expr=ast.WindowFunction( name="sum", - args=[ast.Field(chain=["count"])], + exprs=[ast.Field(chain=["count"])], over_expr=window_expr, ), ), diff --git a/requirements.in b/requirements.in index 72dba7f70ba2e3..2b7efd33c6ddbf 100644 --- a/requirements.in +++ b/requirements.in @@ -93,5 +93,5 @@ phonenumberslite==8.13.6 openai==1.10.0 tiktoken==0.6.0 nh3==0.2.14 -hogql-parser==1.0.11 +hogql-parser==1.0.12 zxcvbn==4.4.28 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index dcc57107014fbd..3ab55989b8abe4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -276,7 +276,7 @@ h11==0.13.0 # wsproto hexbytes==1.0.0 # via dlt -hogql-parser==1.0.11 +hogql-parser==1.0.12 # via -r requirements.in httpcore==1.0.2 # via httpx From e023e537372c26de35ad9797975428fdb1e60444 Mon Sep 17 00:00:00 2001 From: Robbie Date: Tue, 11 Jun 2024 11:07:31 +0100 Subject: [PATCH 27/35] feat(web-analytics): Add missing limit_context to query runners (#22867) Add missing limit_context to query runners --- posthog/hogql_queries/query_runner.py | 41 +++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/posthog/hogql_queries/query_runner.py b/posthog/hogql_queries/query_runner.py index 8fe0c0f3367861..8ea3e2e806e79b 100644 --- a/posthog/hogql_queries/query_runner.py +++ b/posthog/hogql_queries/query_runner.py @@ -264,31 +264,62 @@ def get_query_runner( team=team, timings=timings, modifiers=modifiers, + limit_context=limit_context, ) if kind == "WebOverviewQuery": use_session_table = get_from_dict_or_attr(query, "useSessionsTable") if use_session_table: from .web_analytics.web_overview import WebOverviewQueryRunner - return WebOverviewQueryRunner(query=query, team=team, timings=timings, modifiers=modifiers) + return WebOverviewQueryRunner( + query=query, + team=team, + timings=timings, + modifiers=modifiers, + limit_context=limit_context, + ) else: from .web_analytics.web_overview_legacy import LegacyWebOverviewQueryRunner - return LegacyWebOverviewQueryRunner(query=query, team=team, timings=timings, modifiers=modifiers) + return LegacyWebOverviewQueryRunner( + query=query, + team=team, + timings=timings, + modifiers=modifiers, + limit_context=limit_context, + ) if kind == "WebTopClicksQuery": from .web_analytics.top_clicks import WebTopClicksQueryRunner - return WebTopClicksQueryRunner(query=query, team=team, timings=timings, modifiers=modifiers) + return WebTopClicksQueryRunner( + query=query, + team=team, + timings=timings, + modifiers=modifiers, + limit_context=limit_context, + ) if kind == "WebStatsTableQuery": use_session_table = get_from_dict_or_attr(query, "useSessionsTable") if use_session_table: from .web_analytics.stats_table import WebStatsTableQueryRunner - return WebStatsTableQueryRunner(query=query, team=team, timings=timings, modifiers=modifiers) + return WebStatsTableQueryRunner( + query=query, + team=team, + timings=timings, + modifiers=modifiers, + limit_context=limit_context, + ) else: from .web_analytics.stats_table_legacy import LegacyWebStatsTableQueryRunner - return LegacyWebStatsTableQueryRunner(query=query, team=team, timings=timings, modifiers=modifiers) + return LegacyWebStatsTableQueryRunner( + query=query, + team=team, + timings=timings, + modifiers=modifiers, + limit_context=limit_context, + ) raise ValueError(f"Can't get a runner for an unknown query kind: {kind}") From 278bffee5b7d5dcfe8450a81453f864316ece2f1 Mon Sep 17 00:00:00 2001 From: David Newell Date: Tue, 11 Jun 2024 11:27:33 +0100 Subject: [PATCH 28/35] chore: abstract duration & console log property filters (#22848) --- .../lib/components/PropertyFilters/utils.ts | 10 +-- frontend/src/queries/schema.json | 27 +++++-- .../filters/RecordingsUniversalFilters.tsx | 13 ++++ .../sessionRecordingsPlaylistLogic.ts | 11 ++- frontend/src/types.ts | 13 +++- .../test/test_stickiness_query_runner.py | 4 +- posthog/schema.py | 76 ++++++++++--------- posthog/types.py | 4 +- 8 files changed, 102 insertions(+), 56 deletions(-) diff --git a/frontend/src/lib/components/PropertyFilters/utils.ts b/frontend/src/lib/components/PropertyFilters/utils.ts index 504c32e178748e..762d4c377599a6 100644 --- a/frontend/src/lib/components/PropertyFilters/utils.ts +++ b/frontend/src/lib/components/PropertyFilters/utils.ts @@ -27,7 +27,7 @@ import { PropertyGroupFilterValue, PropertyOperator, PropertyType, - RecordingDurationFilter, + RecordingPropertyFilter, SessionPropertyFilter, } from '~/types' @@ -200,7 +200,7 @@ export function isElementPropertyFilter(filter?: AnyFilterLike | null): filter i export function isSessionPropertyFilter(filter?: AnyFilterLike | null): filter is SessionPropertyFilter { return filter?.type === PropertyFilterType.Session } -export function isRecordingDurationFilter(filter?: AnyFilterLike | null): filter is RecordingDurationFilter { +export function isRecordingPropertyFilter(filter?: AnyFilterLike | null): filter is RecordingPropertyFilter { return filter?.type === PropertyFilterType.Recording } export function isGroupPropertyFilter(filter?: AnyFilterLike | null): filter is GroupPropertyFilter { @@ -223,7 +223,7 @@ export function isAnyPropertyfilter(filter?: AnyFilterLike | null): filter is An isElementPropertyFilter(filter) || isSessionPropertyFilter(filter) || isCohortPropertyFilter(filter) || - isRecordingDurationFilter(filter) || + isRecordingPropertyFilter(filter) || isFeaturePropertyFilter(filter) || isGroupPropertyFilter(filter) ) @@ -236,7 +236,7 @@ export function isPropertyFilterWithOperator( | PersonPropertyFilter | ElementPropertyFilter | SessionPropertyFilter - | RecordingDurationFilter + | RecordingPropertyFilter | FeaturePropertyFilter | GroupPropertyFilter | DataWarehousePropertyFilter { @@ -246,7 +246,7 @@ export function isPropertyFilterWithOperator( isPersonPropertyFilter(filter) || isElementPropertyFilter(filter) || isSessionPropertyFilter(filter) || - isRecordingDurationFilter(filter) || + isRecordingPropertyFilter(filter) || isFeaturePropertyFilter(filter) || isGroupPropertyFilter(filter) || isDataWarehousePropertyFilter(filter)) diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index 38b26efae384c0..d4015b9fa212e8 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -279,7 +279,7 @@ "$ref": "#/definitions/CohortPropertyFilter" }, { - "$ref": "#/definitions/RecordingDurationFilter" + "$ref": "#/definitions/RecordingPropertyFilter" }, { "$ref": "#/definitions/GroupPropertyFilter" @@ -2639,6 +2639,10 @@ "Day": { "type": "integer" }, + "DurationType": { + "enum": ["duration", "active_seconds", "inactive_seconds"], + "type": "string" + }, "ElementPropertyFilter": { "additionalProperties": false, "description": "Sync with plugin-server/src/types.ts", @@ -6964,12 +6968,23 @@ "required": ["k", "t"], "type": "object" }, - "RecordingDurationFilter": { + "RecordingPropertyFilter": { "additionalProperties": false, "properties": { "key": { - "const": "duration", - "type": "string" + "anyOf": [ + { + "$ref": "#/definitions/DurationType" + }, + { + "const": "console_log_level", + "type": "string" + }, + { + "const": "console_log_query", + "type": "string" + } + ] }, "label": { "type": "string" @@ -6982,10 +6997,10 @@ "type": "string" }, "value": { - "type": "number" + "$ref": "#/definitions/PropertyFilterValue" } }, - "required": ["key", "operator", "type", "value"], + "required": ["key", "operator", "type"], "type": "object" }, "RefreshType": { diff --git a/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx b/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx index b43b5f3c550b6f..3e0bec8ff834d6 100644 --- a/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx +++ b/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx @@ -11,6 +11,7 @@ import { cohortsModel } from '~/models/cohortsModel' import { AndOrFilterSelect } from '~/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect' import { sessionRecordingsPlaylistLogic } from '../playlist/sessionRecordingsPlaylistLogic' +import { DurationFilter } from './DurationFilter' export const RecordingsUniversalFilters = (): JSX.Element => { useMountedLogic(cohortsModel) @@ -18,6 +19,8 @@ export const RecordingsUniversalFilters = (): JSX.Element => { const { universalFilters } = useValues(sessionRecordingsPlaylistLogic) const { setUniversalFilters } = useActions(sessionRecordingsPlaylistLogic) + const durationFilter = universalFilters.duration[0] + return (
@@ -44,6 +47,16 @@ export const RecordingsUniversalFilters = (): JSX.Element => { dropdownPlacement="bottom-start" size="small" /> + { + setUniversalFilters({ + duration: [{ ...newRecordingDurationFilter, key: newDurationType }], + }) + }} + recordingDurationFilter={durationFilter} + durationTypeFilter={durationFilter.key} + pageKey="session-recordings" + /> diff --git a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts index 72bdc8f8bb7706..122c80c77de275 100644 --- a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts +++ b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts @@ -97,6 +97,7 @@ export const DEFAULT_RECORDING_UNIVERSAL_FILTERS: RecordingUniversalFilters = { filter_test_accounts: false, date_from: '-3d', filter_group: { ...DEFAULT_UNIVERSAL_GROUP_FILTER }, + duration: [defaultRecordingDurationFilter], } const DEFAULT_PERSON_RECORDING_FILTERS: RecordingFilters = { @@ -134,17 +135,23 @@ function convertUniversalFiltersToLegacyFilters(universalFilters: RecordingUnive } else if (isActionFilter(f)) { actions.push(f) } else if (isAnyPropertyfilter(f)) { - properties.push(f) + if (f.type === PropertyFilterType.Recording) { + // TODO: add console log filtering + } else { + properties.push(f) + } } }) - // TODO: add console log and duration filtering (not yet supported in universal filtering) + const durationFilter = universalFilters.duration[0] return { ...universalFilters, properties, events, actions, + session_recording_duration: { ...durationFilter, key: 'duration' }, + duration_type_filter: durationFilter.key, } } diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 2d6a2ee8fbf6a8..24b63bdc2b14c5 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -778,7 +778,7 @@ export type AnyPropertyFilter = | ElementPropertyFilter | SessionPropertyFilter | CohortPropertyFilter - | RecordingDurationFilter + | RecordingPropertyFilter | GroupPropertyFilter | FeaturePropertyFilter | HogQLPropertyFilter @@ -947,13 +947,17 @@ export type ActionStepProperties = | ElementPropertyFilter | CohortPropertyFilter -export interface RecordingDurationFilter extends BasePropertyFilter { +export interface RecordingPropertyFilter extends BasePropertyFilter { type: PropertyFilterType.Recording - key: 'duration' - value: number + key: DurationType | 'console_log_level' | 'console_log_query' operator: PropertyOperator } +export interface RecordingDurationFilter extends RecordingPropertyFilter { + key: DurationType + value: number +} + export type DurationType = 'duration' | 'active_seconds' | 'inactive_seconds' export type FilterableLogLevel = 'info' | 'warn' | 'error' @@ -981,6 +985,7 @@ export interface RecordingUniversalFilters { live_mode?: boolean date_from?: string | null date_to?: string | null + duration: RecordingDurationFilter[] filter_test_accounts?: boolean filter_group: UniversalFiltersGroup } diff --git a/posthog/hogql_queries/insights/test/test_stickiness_query_runner.py b/posthog/hogql_queries/insights/test/test_stickiness_query_runner.py index 46d1e24842f258..f9a2cbc17a0383 100644 --- a/posthog/hogql_queries/insights/test/test_stickiness_query_runner.py +++ b/posthog/hogql_queries/insights/test/test_stickiness_query_runner.py @@ -27,7 +27,7 @@ PersonPropertyFilter, PropertyGroupFilter, PropertyOperator, - RecordingDurationFilter, + RecordingPropertyFilter, SessionPropertyFilter, StickinessFilter, StickinessQuery, @@ -58,7 +58,7 @@ class SeriesTestData: ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, diff --git a/posthog/schema.py b/posthog/schema.py index aa1bddb107a45b..91a96d1c96eddc 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -292,6 +292,12 @@ class Day(RootModel[int]): root: int +class DurationType(str, Enum): + DURATION = "duration" + ACTIVE_SECONDS = "active_seconds" + INACTIVE_SECONDS = "inactive_seconds" + + class Key(str, Enum): TAG_NAME = "tag_name" TEXT = "text" @@ -863,15 +869,15 @@ class QueryTiming(BaseModel): t: float = Field(..., description="Time in seconds. Shortened to 't' to save on data.") -class RecordingDurationFilter(BaseModel): +class RecordingPropertyFilter(BaseModel): model_config = ConfigDict( extra="forbid", ) - key: Literal["duration"] = "duration" + key: Union[DurationType, str] label: Optional[str] = None operator: PropertyOperator type: Literal["recording"] = "recording" - value: float + value: Optional[Union[str, float, list[Union[str, float]]]] = None class Kind1(str, Enum): @@ -2801,7 +2807,7 @@ class DashboardFilter(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -2854,7 +2860,7 @@ class DataWarehouseNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -2885,7 +2891,7 @@ class DataWarehouseNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -2927,7 +2933,7 @@ class EntityNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -2956,7 +2962,7 @@ class EntityNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -2983,7 +2989,7 @@ class EventsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3014,7 +3020,7 @@ class EventsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3044,7 +3050,7 @@ class EventsQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3073,7 +3079,7 @@ class EventsQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3101,7 +3107,7 @@ class FunnelExclusionActionsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3133,7 +3139,7 @@ class FunnelExclusionActionsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3160,7 +3166,7 @@ class FunnelExclusionEventsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3193,7 +3199,7 @@ class FunnelExclusionEventsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3220,7 +3226,7 @@ class HogQLFilters(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3263,7 +3269,7 @@ class PersonsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3290,7 +3296,7 @@ class PersonsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3318,7 +3324,7 @@ class PropertyGroupFilterValue(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3397,7 +3403,7 @@ class ActionsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3427,7 +3433,7 @@ class ActionsNode(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3536,7 +3542,7 @@ class RetentionQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3578,7 +3584,7 @@ class StickinessQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3627,7 +3633,7 @@ class TrendsQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3688,7 +3694,7 @@ class FilterType(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3732,7 +3738,7 @@ class FunnelsQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3773,7 +3779,7 @@ class InsightsQueryBaseFunnelsQueryResponse(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3811,7 +3817,7 @@ class InsightsQueryBaseLifecycleQueryResponse(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3849,7 +3855,7 @@ class InsightsQueryBasePathsQueryResponse(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3887,7 +3893,7 @@ class InsightsQueryBaseRetentionQueryResponse(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3925,7 +3931,7 @@ class InsightsQueryBaseTrendsQueryResponse(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -3970,7 +3976,7 @@ class LifecycleQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -4147,7 +4153,7 @@ class PathsQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, @@ -4227,7 +4233,7 @@ class FunnelCorrelationActorsQuery(BaseModel): ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, diff --git a/posthog/types.py b/posthog/types.py index 466b537bdea6bf..c5b42dd8de8968 100644 --- a/posthog/types.py +++ b/posthog/types.py @@ -23,7 +23,7 @@ HogQLPropertyFilter, InsightActorsQuery, PersonPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, SessionPropertyFilter, TrendsQuery, FunnelsQuery, @@ -53,7 +53,7 @@ ElementPropertyFilter, SessionPropertyFilter, CohortPropertyFilter, - RecordingDurationFilter, + RecordingPropertyFilter, GroupPropertyFilter, FeaturePropertyFilter, HogQLPropertyFilter, From 09061dbb3a27947ba535dbdfb6b4001d4a616326 Mon Sep 17 00:00:00 2001 From: David Newell Date: Tue, 11 Jun 2024 11:55:58 +0100 Subject: [PATCH 29/35] fix: move add filter button level (#22873) --- .../session-recordings/filters/RecordingsUniversalFilters.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx b/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx index 3e0bec8ff834d6..e60e2ccc569be9 100644 --- a/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx +++ b/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx @@ -106,7 +106,6 @@ export const RecordingsUniversalFilters = (): JSX.Element => { }} > -
@@ -123,6 +122,7 @@ const RecordingsUniversalFilterGroup = (): JSX.Element => { return isUniversalGroupFilterLike(filterOrGroup) ? ( + ) : ( Date: Tue, 11 Jun 2024 09:02:31 -0400 Subject: [PATCH 30/35] feat(data-warehouse): edit sync frequency (#22757) * remove postgres ff * backend * frontend * update migration * Update UI snapshots for `chromium` (2) * comments * update migration * Update UI snapshots for `chromium` (2) * Update UI snapshots for `chromium` (1) * Update UI snapshots for `chromium` (2) * Update UI snapshots for `chromium` (1) * Update UI snapshots for `chromium` (2) * Update UI snapshots for `chromium` (2) * Update UI snapshots for `chromium` (2) * fix tests * Update UI snapshots for `chromium` (1) * Update UI snapshots for `chromium` (2) * Update UI snapshots for `chromium` (1) * Update UI snapshots for `chromium` (1) * Update UI snapshots for `chromium` (2) * Update UI snapshots for `chromium` (1) * Update UI snapshots for `chromium` (2) * Update UI snapshots for `chromium` (1) * Update UI snapshots for `chromium` (2) * Update UI snapshots for `chromium` (1) * Update UI snapshots for `chromium` (2) * Update UI snapshots for `chromium` (2) * format * Update UI snapshots for `chromium` (1) * Update UI snapshots for `chromium` (2) * Update UI snapshots for `chromium` (1) * Restore main --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- frontend/src/lib/api.ts | 6 +++ .../settings/DataWarehouseSourcesTable.tsx | 32 ++++++++++-- .../settings/dataWarehouseSettingsLogic.ts | 11 ++++- frontend/src/types.ts | 3 ++ latest_migrations.manifest | 2 +- .../0426_externaldatasource_sync_frequency.py | 22 +++++++++ posthog/warehouse/api/external_data_source.py | 33 +++++++------ .../api/test/test_external_data_source.py | 49 ++++++++++++++++++- posthog/warehouse/data_load/service.py | 17 +++++-- .../warehouse/models/external_data_source.py | 40 +++++++++++++++ 10 files changed, 190 insertions(+), 25 deletions(-) create mode 100644 posthog/migrations/0426_externaldatasource_sync_frequency.py diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index d83402b59c88d1..d0a94739c95f4d 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -2005,6 +2005,12 @@ const api = { async reload(sourceId: ExternalDataStripeSource['id']): Promise { await new ApiRequest().externalDataSource(sourceId).withAction('reload').create() }, + async update( + sourceId: ExternalDataStripeSource['id'], + data: Partial + ): Promise { + return await new ApiRequest().externalDataSource(sourceId).update({ data }) + }, async database_schema( source_type: ExternalDataSourceType, payload: Record diff --git a/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourcesTable.tsx b/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourcesTable.tsx index d4e9082c55c49a..2a0dd8b31314b2 100644 --- a/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourcesTable.tsx +++ b/frontend/src/scenes/data-warehouse/settings/DataWarehouseSourcesTable.tsx @@ -1,5 +1,15 @@ import { TZLabel } from '@posthog/apps-common' -import { LemonButton, LemonDialog, LemonSwitch, LemonTable, LemonTag, Link, Spinner, Tooltip } from '@posthog/lemon-ui' +import { + LemonButton, + LemonDialog, + LemonSelect, + LemonSwitch, + LemonTable, + LemonTag, + Link, + Spinner, + Tooltip, +} from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { router } from 'kea-router' import { ProductIntroduction } from 'lib/components/ProductIntroduction/ProductIntroduction' @@ -14,10 +24,10 @@ import { urls } from 'scenes/urls' import { DataTableNode, NodeKind } from '~/queries/schema' import { + DataWarehouseSyncInterval, ExternalDataSourceSchema, ExternalDataSourceType, ExternalDataStripeSource, - PipelineInterval, ProductKey, } from '~/types' @@ -33,7 +43,7 @@ const StatusTagSetting = { export function DataWarehouseSourcesTable(): JSX.Element { const { dataWarehouseSources, dataWarehouseSourcesLoading, sourceReloadingById } = useValues(dataWarehouseSettingsLogic) - const { deleteSource, reloadSource } = useActions(dataWarehouseSettingsLogic) + const { deleteSource, reloadSource, updateSource } = useActions(dataWarehouseSettingsLogic) const renderExpandable = (source: ExternalDataStripeSource): JSX.Element => { return ( @@ -90,8 +100,20 @@ export function DataWarehouseSourcesTable(): JSX.Element { { title: 'Sync Frequency', key: 'frequency', - render: function RenderFrequency() { - return 'day' as PipelineInterval + render: function RenderFrequency(_, source) { + return ( + + updateSource({ ...source, sync_frequency: value as DataWarehouseSyncInterval }) + } + options={[ + { value: 'day' as DataWarehouseSyncInterval, label: 'Daily' }, + { value: 'week' as DataWarehouseSyncInterval, label: 'Weekly' }, + { value: 'month' as DataWarehouseSyncInterval, label: 'Monthly' }, + ]} + /> + ) }, }, { diff --git a/frontend/src/scenes/data-warehouse/settings/dataWarehouseSettingsLogic.ts b/frontend/src/scenes/data-warehouse/settings/dataWarehouseSettingsLogic.ts index a9cd46d0360daa..19795ffe3c37ab 100644 --- a/frontend/src/scenes/data-warehouse/settings/dataWarehouseSettingsLogic.ts +++ b/frontend/src/scenes/data-warehouse/settings/dataWarehouseSettingsLogic.ts @@ -25,7 +25,7 @@ export const dataWarehouseSettingsLogic = kea([ updateSchema: (schema: ExternalDataSourceSchema) => ({ schema }), abortAnyRunningQuery: true, }), - loaders(({ cache, actions }) => ({ + loaders(({ cache, actions, values }) => ({ dataWarehouseSources: [ null as PaginatedResponse | null, { @@ -45,6 +45,15 @@ export const dataWarehouseSettingsLogic = kea([ return res }, + updateSource: async (source: ExternalDataStripeSource) => { + const updatedSource = await api.externalDataSources.update(source.id, source) + return { + ...values.dataWarehouseSources, + results: + values.dataWarehouseSources?.results.map((s) => (s.id === updatedSource.id ? source : s)) || + [], + } + }, }, ], })), diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 24b63bdc2b14c5..64b6904a69ae22 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -3763,6 +3763,7 @@ export interface ExternalDataStripeSource { prefix: string last_run_at?: Dayjs schemas: ExternalDataSourceSchema[] + sync_frequency: DataWarehouseSyncInterval } export interface SimpleExternalDataSourceSchema { id: string @@ -3896,6 +3897,8 @@ export type BatchExportService = export type PipelineInterval = 'hour' | 'day' | 'every 5 minutes' +export type DataWarehouseSyncInterval = 'day' | 'week' | 'month' + export type BatchExportConfiguration = { // User provided data for the export. This is the data that the user // provides when creating the export. diff --git a/latest_migrations.manifest b/latest_migrations.manifest index 37040b220d4821..905aeb627006bc 100644 --- a/latest_migrations.manifest +++ b/latest_migrations.manifest @@ -5,7 +5,7 @@ contenttypes: 0002_remove_content_type_name ee: 0016_rolemembership_organization_member otp_static: 0002_throttling otp_totp: 0002_auto_20190420_0723 -posthog: 0425_hogfunction +posthog: 0426_externaldatasource_sync_frequency sessions: 0001_initial social_django: 0010_uid_db_index two_factor: 0007_auto_20201201_1019 diff --git a/posthog/migrations/0426_externaldatasource_sync_frequency.py b/posthog/migrations/0426_externaldatasource_sync_frequency.py new file mode 100644 index 00000000000000..6bb13966e591b9 --- /dev/null +++ b/posthog/migrations/0426_externaldatasource_sync_frequency.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.11 on 2024-06-06 15:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("posthog", "0425_hogfunction"), + ] + + operations = [ + migrations.AddField( + model_name="externaldatasource", + name="sync_frequency", + field=models.CharField( + blank=True, + choices=[("day", "Daily"), ("week", "Weekly"), ("month", "Monthly")], + default="day", + max_length=128, + ), + ), + ] diff --git a/posthog/warehouse/api/external_data_source.py b/posthog/warehouse/api/external_data_source.py index c53717207f7fd3..3b129ed275c978 100644 --- a/posthog/warehouse/api/external_data_source.py +++ b/posthog/warehouse/api/external_data_source.py @@ -12,7 +12,6 @@ from posthog.api.routing import TeamAndOrgViewSetMixin from posthog.warehouse.data_load.service import ( sync_external_data_job_workflow, - trigger_external_data_workflow, delete_external_data_schedule, cancel_external_data_workflow, delete_data_import_folder, @@ -78,8 +77,18 @@ class Meta: "prefix", "last_run_at", "schemas", + "sync_frequency", + ] + read_only_fields = [ + "id", + "created_by", + "created_at", + "status", + "source_type", + "last_run_at", + "schemas", + "prefix", ] - read_only_fields = ["id", "created_by", "created_at", "status", "source_type", "last_run_at", "schemas"] def get_last_run_at(self, instance: ExternalDataSource) -> str: latest_completed_run = ( @@ -116,6 +125,12 @@ def get_schemas(self, instance: ExternalDataSource): schemas = instance.schemas.order_by("name").all() return ExternalDataSchemaSerializer(schemas, many=True, read_only=True, context=self.context).data + def update(self, instance: ExternalDataSource, validated_data: Any) -> Any: + updated_source: ExternalDataSource = super().update(instance, validated_data) + updated_source.update_schemas() + + return updated_source + class SimpleExternalDataSourceSerializers(serializers.ModelSerializer): class Meta: @@ -448,7 +463,7 @@ def destroy(self, request: Request, *args: Any, **kwargs: Any) -> Response: @action(methods=["POST"], detail=True) def reload(self, request: Request, *args: Any, **kwargs: Any): - instance = self.get_object() + instance: ExternalDataSource = self.get_object() if is_any_external_data_job_paused(self.team_id): return Response( @@ -461,17 +476,7 @@ def reload(self, request: Request, *args: Any, **kwargs: Any): except temporalio.service.RPCError: # if the source schedule has been removed - trigger the schema schedules - for schema in ExternalDataSchema.objects.filter( - team_id=self.team_id, source_id=instance.id, should_sync=True - ).all(): - try: - trigger_external_data_workflow(schema) - except temporalio.service.RPCError as e: - if e.status == temporalio.service.RPCStatusCode.NOT_FOUND: - sync_external_data_job_workflow(schema, create=True) - - except Exception as e: - logger.exception(f"Could not trigger external data job for schema {schema.name}", exc_info=e) + instance.reload_schemas() except Exception as e: logger.exception("Could not trigger external data job", exc_info=e) diff --git a/posthog/warehouse/api/test/test_external_data_source.py b/posthog/warehouse/api/test/test_external_data_source.py index 1c23807b823289..da7fb63d7e1050 100644 --- a/posthog/warehouse/api/test/test_external_data_source.py +++ b/posthog/warehouse/api/test/test_external_data_source.py @@ -6,10 +6,14 @@ from posthog.temporal.data_imports.pipelines.schemas import ( PIPELINE_TYPE_SCHEMA_DEFAULT_MAPPING, ) +from posthog.warehouse.data_load.service import get_sync_schedule from django.test import override_settings from django.conf import settings from posthog.models import Team import psycopg +from rest_framework import status + +import datetime class TestSavedQuery(APIBaseTest): @@ -102,7 +106,17 @@ def test_get_external_data_source_with_schema(self): self.assertEqual(response.status_code, 200) self.assertListEqual( list(payload.keys()), - ["id", "created_at", "created_by", "status", "source_type", "prefix", "last_run_at", "schemas"], + [ + "id", + "created_at", + "created_by", + "status", + "source_type", + "prefix", + "last_run_at", + "schemas", + "sync_frequency", + ], ) self.assertEqual( payload["schemas"], @@ -280,3 +294,36 @@ def test_internal_postgres(self, patch_get_postgres_schemas): ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json(), {"message": "Cannot use internal Postgres database"}) + + @patch("posthog.warehouse.data_load.service.sync_external_data_job_workflow") + def test_update_source_sync_frequency(self, _patch_sync_external_data_job_workflow): + source = self._create_external_data_source() + schema = self._create_external_data_schema(source.pk) + + self.assertEqual(source.sync_frequency, ExternalDataSource.SyncFrequency.DAILY) + # test schedule + schedule = get_sync_schedule(schema) + self.assertEqual( + schedule.spec.intervals[0].every, + datetime.timedelta(days=1), + ) + + # test api + response = self.client.patch( + f"/api/projects/{self.team.id}/external_data_sources/{source.pk}/", + data={"sync_frequency": ExternalDataSource.SyncFrequency.WEEKLY}, + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + source.refresh_from_db() + schema.refresh_from_db() + + self.assertEqual(source.sync_frequency, ExternalDataSource.SyncFrequency.WEEKLY) + self.assertEqual(_patch_sync_external_data_job_workflow.call_count, 1) + + # test schedule + schedule = get_sync_schedule(schema) + self.assertEqual( + schedule.spec.intervals[0].every, + datetime.timedelta(days=7), + ) diff --git a/posthog/warehouse/data_load/service.py b/posthog/warehouse/data_load/service.py index 688ee42b788b59..c3f97406c56cf4 100644 --- a/posthog/warehouse/data_load/service.py +++ b/posthog/warehouse/data_load/service.py @@ -46,6 +46,8 @@ def get_sync_schedule(external_data_schema: ExternalDataSchema): external_data_source_id=external_data_schema.source_id, ) + sync_frequency = get_sync_frequency(external_data_schema) + return Schedule( action=ScheduleActionStartWorkflow( "external-data-job", @@ -55,9 +57,7 @@ def get_sync_schedule(external_data_schema: ExternalDataSchema): ), spec=ScheduleSpec( intervals=[ - ScheduleIntervalSpec( - every=timedelta(hours=24), offset=timedelta(hours=external_data_schema.created_at.hour) - ) + ScheduleIntervalSpec(every=sync_frequency, offset=timedelta(hours=external_data_schema.created_at.hour)) ], jitter=timedelta(hours=2), ), @@ -66,6 +66,17 @@ def get_sync_schedule(external_data_schema: ExternalDataSchema): ) +def get_sync_frequency(external_data_schema: ExternalDataSchema): + if external_data_schema.source.sync_frequency == ExternalDataSource.SyncFrequency.DAILY: + return timedelta(days=1) + elif external_data_schema.source.sync_frequency == ExternalDataSource.SyncFrequency.WEEKLY: + return timedelta(weeks=1) + elif external_data_schema.source.sync_frequency == ExternalDataSource.SyncFrequency.MONTHLY: + return timedelta(days=30) + else: + raise ValueError(f"Unknown sync frequency: {external_data_schema.source.sync_frequency}") + + def sync_external_data_job_workflow( external_data_schema: ExternalDataSchema, create: bool = False ) -> ExternalDataSchema: diff --git a/posthog/warehouse/models/external_data_source.py b/posthog/warehouse/models/external_data_source.py index 3b0c8f45aaf6b1..58aa891c34db05 100644 --- a/posthog/warehouse/models/external_data_source.py +++ b/posthog/warehouse/models/external_data_source.py @@ -6,6 +6,11 @@ from posthog.warehouse.util import database_sync_to_async from uuid import UUID +import structlog +import temporalio + +logger = structlog.get_logger(__name__) + class ExternalDataSource(CreatedMetaFields, UUIDModel): class Type(models.TextChoices): @@ -22,11 +27,21 @@ class Status(models.TextChoices): COMPLETED = "Completed", "Completed" CANCELLED = "Cancelled", "Cancelled" + class SyncFrequency(models.TextChoices): + DAILY = "day", "Daily" + WEEKLY = "week", "Weekly" + MONTHLY = "month", "Monthly" + # TODO provide flexible schedule definition + source_id: models.CharField = models.CharField(max_length=400) connection_id: models.CharField = models.CharField(max_length=400) destination_id: models.CharField = models.CharField(max_length=400, null=True, blank=True) team: models.ForeignKey = models.ForeignKey(Team, on_delete=models.CASCADE) + sync_frequency: models.CharField = models.CharField( + max_length=128, choices=SyncFrequency.choices, default=SyncFrequency.DAILY, blank=True + ) + # `status` is deprecated in favour of external_data_schema.status status: models.CharField = models.CharField(max_length=400) source_type: models.CharField = models.CharField(max_length=128, choices=Type.choices) @@ -38,6 +53,31 @@ class Status(models.TextChoices): __repr__ = sane_repr("id") + def reload_schemas(self): + from posthog.warehouse.models.external_data_schema import ExternalDataSchema + from posthog.warehouse.data_load.service import sync_external_data_job_workflow, trigger_external_data_workflow + + for schema in ExternalDataSchema.objects.filter( + team_id=self.team.pk, source_id=self.id, should_sync=True + ).all(): + try: + trigger_external_data_workflow(schema) + except temporalio.service.RPCError as e: + if e.status == temporalio.service.RPCStatusCode.NOT_FOUND: + sync_external_data_job_workflow(schema, create=True) + + except Exception as e: + logger.exception(f"Could not trigger external data job for schema {schema.name}", exc_info=e) + + def update_schemas(self): + from posthog.warehouse.models.external_data_schema import ExternalDataSchema + from posthog.warehouse.data_load.service import sync_external_data_job_workflow + + for schema in ExternalDataSchema.objects.filter( + team_id=self.team.pk, source_id=self.id, should_sync=True + ).all(): + sync_external_data_job_workflow(schema, create=False) + @database_sync_to_async def get_external_data_source(source_id: UUID) -> ExternalDataSource: From 3e9825241774bc18d4ab92854c8e88a0d1d4497d Mon Sep 17 00:00:00 2001 From: David Newell Date: Tue, 11 Jun 2024 14:13:22 +0100 Subject: [PATCH 31/35] chore: remove unused colors (#22879) --- .../player/controller/Seekbar.scss | 4 ++-- frontend/src/styles/vars.scss | 23 +------------------ 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/frontend/src/scenes/session-recordings/player/controller/Seekbar.scss b/frontend/src/scenes/session-recordings/player/controller/Seekbar.scss index 943f17aa977baf..8549d99d48ddec 100644 --- a/frontend/src/scenes/session-recordings/player/controller/Seekbar.scss +++ b/frontend/src/scenes/session-recordings/player/controller/Seekbar.scss @@ -47,7 +47,7 @@ .PlayerSeekbar__currentbar { z-index: 3; - background-color: var(--recording-seekbar-red); + background-color: var(--primary-3000); border-radius: var(--bar-height) 0 0 var(--bar-height); } @@ -76,7 +76,7 @@ width: var(--thumb-size); height: var(--thumb-size); margin-top: calc(var(--thumb-size) / 2 * -1); - background-color: var(--recording-seekbar-red); + background-color: var(--primary-3000); border: 2px solid var(--bg-light); border-radius: 50%; transition: top 150ms ease-in-out; diff --git a/frontend/src/styles/vars.scss b/frontend/src/styles/vars.scss index 783611a4d6a56c..87581492909179 100644 --- a/frontend/src/styles/vars.scss +++ b/frontend/src/styles/vars.scss @@ -156,13 +156,11 @@ $colors: ( // These vars are modified via SCSS for legacy reasons (e.g. darken/lighten), so keeping as SCSS vars for now. $_primary: map.get($colors, 'primary'); $_success: map.get($colors, 'success'); -$_danger: map.get($colors, 'danger'); -$_primary_bg_hover: rgba($_primary, 0.1); $_primary_bg_active: rgba($_primary, 0.2); $_lifecycle_new: $_primary; $_lifecycle_returning: $_success; $_lifecycle_resurrecting: #a56eff; // --data-lilac -$_lifecycle_dormant: $_danger; +$_lifecycle_dormant: map.get($colors, 'danger'); // root variables are defined as a mixin here because // the toolbar needs them attached to :host not :root @@ -193,9 +191,6 @@ $_lifecycle_dormant: $_danger; --green: var(--success); --black: var(--default); - // Tag colors - --purple-light: #dcb1e3; - //// Data colors (e.g. insight series). Note: colors.ts relies on these values being hexadecimal --data-color-1: #1d4aff; --data-color-2: #621da6; @@ -227,22 +222,6 @@ $_lifecycle_dormant: $_danger; // TODO: unify with lib/colors.ts, getGraphColors() --funnel-axis: var(--border); --funnel-grid: #ddd; - --antd-table-background-dark: #fafafa; - - // Session Recording - --recording-spacing: calc(2rem / 3); - --recording-player-container-bg: #797973; - --recording-buffer-bg: #faaf8c; - --recording-seekbar-red: var(--brand-red); - --recording-hover-event: var(--primary-bg-hover); - --recording-hover-event-mid: var(--primary-bg-active); - --recording-hover-event-dark: var(--primary-3000); - --recording-current-event: #eef2ff; - --recording-current-event-dark: var(--primary-alt); - --recording-failure-event: #fee9e2; - --recording-failure-event-dark: #cd3000; - --recording-highlight-event: var(--mark); - --recording-highlight-event-dark: #946508; // Z-indexes --z-bottom-notice: 5100; From a8cf4ec997a9361019f46b8e11b9b6f4a31a32e1 Mon Sep 17 00:00:00 2001 From: Tom Owers Date: Tue, 11 Jun 2024 14:19:10 +0100 Subject: [PATCH 32/35] chore(hogql): Added corr clickhouse func (#22880) * Added corr clickhouse func * Moved corr into agg funcs --- posthog/hogql/functions/mapping.py | 1 + 1 file changed, 1 insertion(+) diff --git a/posthog/hogql/functions/mapping.py b/posthog/hogql/functions/mapping.py index 2c7052905d46d0..bda37830c4b37c 100644 --- a/posthog/hogql/functions/mapping.py +++ b/posthog/hogql/functions/mapping.py @@ -854,6 +854,7 @@ def compare_types(arg_types: list[ConstantType], sig_arg_types: tuple[ConstantTy "covarPopIf": HogQLFunctionMeta("covarPopIf", 3, 3, aggregate=True), "covarSamp": HogQLFunctionMeta("covarSamp", 2, 2, aggregate=True), "covarSampIf": HogQLFunctionMeta("covarSampIf", 3, 3, aggregate=True), + "corr": HogQLFunctionMeta("corr", 2, 2, aggregate=True), # ClickHouse-specific aggregate functions "anyHeavy": HogQLFunctionMeta("anyHeavy", 1, 1, aggregate=True), "anyHeavyIf": HogQLFunctionMeta("anyHeavyIf", 2, 2, aggregate=True), From 7dbbcb844c7aa51b46d350d40e8572ca837d4bb0 Mon Sep 17 00:00:00 2001 From: Juraj Majerik Date: Tue, 11 Jun 2024 15:21:53 +0200 Subject: [PATCH 33/35] feat(surveys branching): add UI to select branching type for a question (#22866) --- frontend/src/lib/constants.tsx | 1 + .../scenes/surveys/QuestionBranchingInput.tsx | 68 +++++++++++++++++++ .../scenes/surveys/SurveyEditQuestionRow.tsx | 8 +++ frontend/src/scenes/surveys/surveyLogic.tsx | 64 +++++++++++++++++ frontend/src/types.ts | 35 ++++++++++ 5 files changed, 176 insertions(+) create mode 100644 frontend/src/scenes/surveys/QuestionBranchingInput.tsx diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index 6cb2f8eafc6b5f..47903d65943cf7 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -211,6 +211,7 @@ export const FEATURE_FLAGS = { SESSION_REPLAY_UNIVERSAL_FILTERS: 'session-replay-universal-filters', // owner: #team-replay ALERTS: 'alerts', // owner: github.com/nikitaevg SETTINGS_BOUNCE_RATE_PAGE_VIEW_MODE: 'settings-bounce-rate-page-view-mode', // owner: @robbie-c + SURVEYS_BRANCHING_LOGIC: 'surveys-branching-logic', // owner: @jurajmajerik #team-feature-success } as const export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS] diff --git a/frontend/src/scenes/surveys/QuestionBranchingInput.tsx b/frontend/src/scenes/surveys/QuestionBranchingInput.tsx new file mode 100644 index 00000000000000..96c6ea55912d6b --- /dev/null +++ b/frontend/src/scenes/surveys/QuestionBranchingInput.tsx @@ -0,0 +1,68 @@ +import './EditSurvey.scss' + +import { LemonSelect } from '@posthog/lemon-ui' +import { useActions, useValues } from 'kea' +import { LemonField } from 'lib/lemon-ui/LemonField' + +import { MultipleSurveyQuestion, RatingSurveyQuestion, SurveyQuestionBranchingType } from '~/types' + +import { surveyLogic } from './surveyLogic' + +export function QuestionBranchingInput({ + questionIndex, + question, +}: { + questionIndex: number + question: RatingSurveyQuestion | MultipleSurveyQuestion +}): JSX.Element { + const { survey, getBranchingDropdownValue } = useValues(surveyLogic) + const { setQuestionBranching } = useActions(surveyLogic) + + const availableNextQuestions = survey.questions + .map((question, questionIndex) => ({ + ...question, + questionIndex, + })) + .filter((_, idx) => questionIndex !== idx) + const branchingDropdownValue = getBranchingDropdownValue(questionIndex, question) + + return ( + <> + + setQuestionBranching(questionIndex, value)} + options={[ + ...(questionIndex < survey.questions.length - 1 + ? [ + { + label: 'Next question', + value: SurveyQuestionBranchingType.NextQuestion, + }, + ] + : []), + { + label: 'Confirmation message', + value: SurveyQuestionBranchingType.ConfirmationMessage, + }, + { + label: 'Specific question based on answer', + value: SurveyQuestionBranchingType.ResponseBased, + }, + ...availableNextQuestions.map((question) => ({ + label: `${question.questionIndex + 1}. ${question.question}`, + value: `${SurveyQuestionBranchingType.SpecificQuestion}:${question.questionIndex}`, + })), + ]} + /> + + {branchingDropdownValue === SurveyQuestionBranchingType.ResponseBased && ( +
+ TODO: dropdowns for the response-based branching +
+ )} + + ) +} diff --git a/frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx b/frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx index 4b43be07bcf460..ec3b03d54b41df 100644 --- a/frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx +++ b/frontend/src/scenes/surveys/SurveyEditQuestionRow.tsx @@ -7,12 +7,15 @@ import { IconPlusSmall, IconTrash } from '@posthog/icons' import { LemonButton, LemonCheckbox, LemonInput, LemonSelect } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { Group } from 'kea-forms' +import { FEATURE_FLAGS } from 'lib/constants' import { SortableDragIcon } from 'lib/lemon-ui/icons' import { LemonField } from 'lib/lemon-ui/LemonField' +import { featureFlagLogic as enabledFeaturesLogic } from 'lib/logic/featureFlagLogic' import { Survey, SurveyQuestionType } from '~/types' import { defaultSurveyFieldValues, NewSurvey, SurveyQuestionLabel } from './constants' +import { QuestionBranchingInput } from './QuestionBranchingInput' import { HTMLEditor } from './SurveyAppearanceUtils' import { surveyLogic } from './surveyLogic' @@ -85,6 +88,10 @@ export function SurveyEditQuestionHeader({ export function SurveyEditQuestionGroup({ index, question }: { index: number; question: any }): JSX.Element { const { survey, descriptionContentType } = useValues(surveyLogic) const { setDefaultForQuestionType, setSurveyValue } = useActions(surveyLogic) + const { featureFlags } = useValues(enabledFeaturesLogic) + const hasBranching = + featureFlags[FEATURE_FLAGS.SURVEYS_BRANCHING_LOGIC] && + (question.type === SurveyQuestionType.Rating || question.type === SurveyQuestionType.SingleChoice) const initialDescriptionContentType = descriptionContentType(index) ?? 'text' @@ -332,6 +339,7 @@ export function SurveyEditQuestionGroup({ index, question }: { index: number; qu } /> + {hasBranching && }
) diff --git a/frontend/src/scenes/surveys/surveyLogic.tsx b/frontend/src/scenes/surveys/surveyLogic.tsx index c976e7f5dfb535..9b3964b64fdf3d 100644 --- a/frontend/src/scenes/surveys/surveyLogic.tsx +++ b/frontend/src/scenes/surveys/surveyLogic.tsx @@ -16,10 +16,13 @@ import { hogql } from '~/queries/utils' import { Breadcrumb, FeatureFlagFilters, + MultipleSurveyQuestion, PropertyFilterType, PropertyOperator, + RatingSurveyQuestion, Survey, SurveyQuestionBase, + SurveyQuestionBranchingType, SurveyQuestionType, SurveyUrlMatchType, } from '~/types' @@ -154,6 +157,7 @@ export const surveyLogic = kea([ isEditingDescription, isEditingThankYouMessage, }), + setQuestionBranching: (questionIndex, value) => ({ questionIndex, value }), archiveSurvey: true, setWritingHTMLDescription: (writingHTML: boolean) => ({ writingHTML }), setSurveyTemplateValues: (template: any) => ({ template }), @@ -657,6 +661,44 @@ export const surveyLogic = kea([ const newTemplateSurvey = { ...NEW_SURVEY, ...template } return newTemplateSurvey }, + setQuestionBranching: (state, { questionIndex, value }) => { + const newQuestions = [...state.questions] + const question = newQuestions[questionIndex] + + if ( + question.type !== SurveyQuestionType.Rating && + question.type !== SurveyQuestionType.SingleChoice + ) { + throw new Error( + `Survey question type must be ${SurveyQuestionType.Rating} or ${SurveyQuestionType.SingleChoice}` + ) + } + + if (value === SurveyQuestionBranchingType.NextQuestion) { + delete question.branching + } else if (value === SurveyQuestionBranchingType.ConfirmationMessage) { + question.branching = { + type: SurveyQuestionBranchingType.ConfirmationMessage, + } + } else if (value === SurveyQuestionBranchingType.ResponseBased) { + question.branching = { + type: SurveyQuestionBranchingType.ResponseBased, + responseValue: {}, + } + } else if (value.startsWith(SurveyQuestionBranchingType.SpecificQuestion)) { + const nextQuestionIndex = parseInt(value.split(':')[1]) + question.branching = { + type: SurveyQuestionBranchingType.SpecificQuestion, + index: nextQuestionIndex, + } + } + + newQuestions[questionIndex] = question + return { + ...state, + questions: newQuestions, + } + }, }, ], selectedPageIndex: [ @@ -882,6 +924,28 @@ export const surveyLogic = kea([ } }, ], + getBranchingDropdownValue: [ + (s) => [s.survey], + (survey) => (questionIndex: number, question: RatingSurveyQuestion | MultipleSurveyQuestion) => { + if (question.branching?.type) { + const { type } = question.branching + + if (type === SurveyQuestionBranchingType.SpecificQuestion) { + const nextQuestionIndex = question.branching.index + return `${SurveyQuestionBranchingType.SpecificQuestion}:${nextQuestionIndex}` + } + + return type + } + + // No branching specified, default to Next question / Confirmation message + if (questionIndex < survey.questions.length - 1) { + return SurveyQuestionBranchingType.NextQuestion + } + + return SurveyQuestionBranchingType.ConfirmationMessage + }, + ], }), forms(({ actions, props, values }) => ({ survey: { diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 64b6904a69ae22..d9ba47ab428d45 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -2666,6 +2666,11 @@ export interface RatingSurveyQuestion extends SurveyQuestionBase { scale: number lowerBoundLabel: string upperBoundLabel: string + branching?: + | NextQuestionBranching + | ConfirmationMessageBranching + | ResponseBasedBranching + | SpecificQuestionBranching } export interface MultipleSurveyQuestion extends SurveyQuestionBase { @@ -2673,6 +2678,11 @@ export interface MultipleSurveyQuestion extends SurveyQuestionBase { choices: string[] shuffleOptions?: boolean hasOpenChoice?: boolean + branching?: + | NextQuestionBranching + | ConfirmationMessageBranching + | ResponseBasedBranching + | SpecificQuestionBranching } export type SurveyQuestion = BasicSurveyQuestion | LinkSurveyQuestion | RatingSurveyQuestion | MultipleSurveyQuestion @@ -2685,6 +2695,31 @@ export enum SurveyQuestionType { Link = 'link', } +export enum SurveyQuestionBranchingType { + NextQuestion = 'next_question', + ConfirmationMessage = 'confirmation_message', + ResponseBased = 'response_based', + SpecificQuestion = 'specific_question', +} + +interface NextQuestionBranching { + type: SurveyQuestionBranchingType.NextQuestion +} + +interface ConfirmationMessageBranching { + type: SurveyQuestionBranchingType.ConfirmationMessage +} + +interface ResponseBasedBranching { + type: SurveyQuestionBranchingType.ResponseBased + responseValue: Record +} + +interface SpecificQuestionBranching { + type: SurveyQuestionBranchingType.SpecificQuestion + index: number +} + export interface FeatureFlagGroupType { properties?: AnyPropertyFilter[] rollout_percentage?: number | null From 0ec897a8b3bf59242a2f254d2960cc5a543af774 Mon Sep 17 00:00:00 2001 From: David Newell Date: Tue, 11 Jun 2024 14:52:39 +0100 Subject: [PATCH 34/35] feat: universal console log property filters (#22869) --- .../components/PropertyValue.tsx | 2 +- .../lib/components/PropertyFilters/utils.ts | 35 +++--- .../TaxonomicFilter/InfiniteSelectResults.tsx | 2 +- .../TaxonomicFilter/InlineHogQLEditor.tsx | 2 +- .../TaxonomicFilter/taxonomicFilterLogic.tsx | 8 ++ .../lib/components/TaxonomicFilter/types.ts | 4 +- .../UniversalFilters.stories.tsx | 5 +- .../UniversalFilters/UniversalFilters.tsx | 15 +-- .../universalFiltersLogic.test.ts | 5 +- .../UniversalFilters/universalFiltersLogic.ts | 32 +++-- frontend/src/lib/taxonomy.tsx | 11 ++ .../src/models/propertyDefinitionsModel.ts | 25 +++- .../filters/RecordingsUniversalFilters.tsx | 5 +- .../filters/ReplayTaxonomicFilters.tsx | 109 ++++++++++++++++++ .../player/playerSettingsLogic.ts | 10 +- .../sessionRecordingsPlaylistLogic.ts | 11 +- 16 files changed, 227 insertions(+), 54 deletions(-) create mode 100644 frontend/src/scenes/session-recordings/filters/ReplayTaxonomicFilters.tsx diff --git a/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx b/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx index 673bd426629bfe..427ad6daf6b5ad 100644 --- a/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx +++ b/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx @@ -128,7 +128,7 @@ export function PropertyValue({ loading={options[propertyKey]?.status === 'loading'} value={formattedValues} mode={isMultiSelect ? 'multiple' : 'single'} - allowCustomValues + allowCustomValues={options[propertyKey]?.allowCustomValues} onChange={(nextVal) => (isMultiSelect ? setValue(nextVal) : setValue(nextVal[0]))} onInputChange={onSearchTextChange} placeholder={placeholder} diff --git a/frontend/src/lib/components/PropertyFilters/utils.ts b/frontend/src/lib/components/PropertyFilters/utils.ts index 762d4c377599a6..da753040497caa 100644 --- a/frontend/src/lib/components/PropertyFilters/utils.ts +++ b/frontend/src/lib/components/PropertyFilters/utils.ts @@ -89,22 +89,21 @@ export function convertPropertyGroupToProperties( return properties } -export const PROPERTY_FILTER_TYPE_TO_TAXONOMIC_FILTER_GROUP_TYPE: Omit< - Record, - PropertyFilterType.Recording // Recording filters are not part of the taxonomic filter, only Replay-specific UI -> = { - [PropertyFilterType.Meta]: TaxonomicFilterGroupType.Metadata, - [PropertyFilterType.Person]: TaxonomicFilterGroupType.PersonProperties, - [PropertyFilterType.Event]: TaxonomicFilterGroupType.EventProperties, - [PropertyFilterType.Feature]: TaxonomicFilterGroupType.EventFeatureFlags, - [PropertyFilterType.Cohort]: TaxonomicFilterGroupType.Cohorts, - [PropertyFilterType.Element]: TaxonomicFilterGroupType.Elements, - [PropertyFilterType.Session]: TaxonomicFilterGroupType.SessionProperties, - [PropertyFilterType.HogQL]: TaxonomicFilterGroupType.HogQLExpression, - [PropertyFilterType.Group]: TaxonomicFilterGroupType.GroupsPrefix, - [PropertyFilterType.DataWarehouse]: TaxonomicFilterGroupType.DataWarehouse, - [PropertyFilterType.DataWarehousePersonProperty]: TaxonomicFilterGroupType.DataWarehousePersonProperties, -} +export const PROPERTY_FILTER_TYPE_TO_TAXONOMIC_FILTER_GROUP_TYPE: Record = + { + [PropertyFilterType.Meta]: TaxonomicFilterGroupType.Metadata, + [PropertyFilterType.Person]: TaxonomicFilterGroupType.PersonProperties, + [PropertyFilterType.Event]: TaxonomicFilterGroupType.EventProperties, + [PropertyFilterType.Feature]: TaxonomicFilterGroupType.EventFeatureFlags, + [PropertyFilterType.Cohort]: TaxonomicFilterGroupType.Cohorts, + [PropertyFilterType.Element]: TaxonomicFilterGroupType.Elements, + [PropertyFilterType.Session]: TaxonomicFilterGroupType.SessionProperties, + [PropertyFilterType.HogQL]: TaxonomicFilterGroupType.HogQLExpression, + [PropertyFilterType.Group]: TaxonomicFilterGroupType.GroupsPrefix, + [PropertyFilterType.DataWarehouse]: TaxonomicFilterGroupType.DataWarehouse, + [PropertyFilterType.DataWarehousePersonProperty]: TaxonomicFilterGroupType.DataWarehousePersonProperties, + [PropertyFilterType.Recording]: TaxonomicFilterGroupType.Replay, + } export function formatPropertyLabel( item: Record, @@ -345,6 +344,10 @@ export function taxonomicFilterTypeToPropertyFilterType( return PropertyFilterType.DataWarehousePersonProperty } + if (filterType == TaxonomicFilterGroupType.Replay) { + return PropertyFilterType.Recording + } + return Object.entries(propertyFilterMapping).find(([, v]) => v === filterType)?.[0] as | PropertyFilterType | undefined diff --git a/frontend/src/lib/components/TaxonomicFilter/InfiniteSelectResults.tsx b/frontend/src/lib/components/TaxonomicFilter/InfiniteSelectResults.tsx index 89bf8bbd9b2f3a..f9579316b153f7 100644 --- a/frontend/src/lib/components/TaxonomicFilter/InfiniteSelectResults.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/InfiniteSelectResults.tsx @@ -68,7 +68,7 @@ export function InfiniteSelectResults({ selectItem(activeTaxonomicGroup, newValue, newValue)} + onChange={(newValue, item) => selectItem(activeTaxonomicGroup, newValue, item)} /> ) : ( diff --git a/frontend/src/lib/components/TaxonomicFilter/InlineHogQLEditor.tsx b/frontend/src/lib/components/TaxonomicFilter/InlineHogQLEditor.tsx index afc7919bbe72ba..b90fe2aadf5d95 100644 --- a/frontend/src/lib/components/TaxonomicFilter/InlineHogQLEditor.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/InlineHogQLEditor.tsx @@ -5,7 +5,7 @@ import { AnyDataNode } from '~/queries/schema' export interface InlineHogQLEditorProps { value?: TaxonomicFilterValue - onChange: (value: TaxonomicFilterValue) => void + onChange: (value: TaxonomicFilterValue, item?: any) => void metadataSource?: AnyDataNode } diff --git a/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx b/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx index 8b328a689d3896..4c97fc48fc3e83 100644 --- a/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx @@ -22,6 +22,7 @@ import { dataWarehouseSceneLogic } from 'scenes/data-warehouse/external/dataWare import { experimentsLogic } from 'scenes/experiments/experimentsLogic' import { featureFlagsLogic } from 'scenes/feature-flags/featureFlagsLogic' import { groupDisplayId } from 'scenes/persons/GroupActorDisplay' +import { ReplayTaxonomicFilters } from 'scenes/session-recordings/filters/ReplayTaxonomicFilters' import { teamLogic } from 'scenes/teamLogic' import { actionsModel } from '~/models/actionsModel' @@ -506,6 +507,13 @@ export const taxonomicFilterLogic = kea([ getPopoverHeader: () => 'HogQL', componentProps: { metadataSource }, }, + { + name: 'Replay', + searchPlaceholder: 'Replay', + type: TaxonomicFilterGroupType.Replay, + render: ReplayTaxonomicFilters, + getPopoverHeader: () => 'Replay', + }, ...groupAnalyticsTaxonomicGroups, ...groupAnalyticsTaxonomicGroupNames, ] diff --git a/frontend/src/lib/components/TaxonomicFilter/types.ts b/frontend/src/lib/components/TaxonomicFilter/types.ts index 787112ca04ea18..f9743bb18764ef 100644 --- a/frontend/src/lib/components/TaxonomicFilter/types.ts +++ b/frontend/src/lib/components/TaxonomicFilter/types.ts @@ -47,7 +47,7 @@ export type TaxonomicFilterValue = string | number | null export type TaxonomicFilterRender = (props: { value?: TaxonomicFilterValue - onChange: (value: TaxonomicFilterValue) => void + onChange: (value: TaxonomicFilterValue, item: any) => void }) => JSX.Element | null export interface TaxonomicFilterGroup { @@ -108,6 +108,8 @@ export enum TaxonomicFilterGroupType { SessionProperties = 'session_properties', HogQLExpression = 'hogql_expression', Notebooks = 'notebooks', + // Misc + Replay = 'replay', } export interface InfiniteListLogicProps extends TaxonomicFilterLogicProps { diff --git a/frontend/src/lib/components/UniversalFilters/UniversalFilters.stories.tsx b/frontend/src/lib/components/UniversalFilters/UniversalFilters.stories.tsx index d1019f26cf5b0b..0fb9248356ff01 100644 --- a/frontend/src/lib/components/UniversalFilters/UniversalFilters.stories.tsx +++ b/frontend/src/lib/components/UniversalFilters/UniversalFilters.stories.tsx @@ -58,8 +58,9 @@ export const Default: StoryFn = ({ group }) => { void - taxonomicEntityFilterGroupTypes: TaxonomicFilterGroupType[] - taxonomicPropertyFilterGroupTypes: TaxonomicFilterGroupType[] + taxonomicGroupTypes: TaxonomicFilterGroupType[] children?: React.ReactNode } @@ -35,8 +34,7 @@ function UniversalFilters({ rootKey, group = null, onChange, - taxonomicEntityFilterGroupTypes, - taxonomicPropertyFilterGroupTypes, + taxonomicGroupTypes, children, }: UniversalFiltersProps): JSX.Element { return ( @@ -46,8 +44,7 @@ function UniversalFilters({ rootKey, group, onChange, - taxonomicEntityFilterGroupTypes, - taxonomicPropertyFilterGroupTypes, + taxonomicGroupTypes, }} > {children} @@ -64,8 +61,7 @@ function Group({ index: number children: React.ReactNode }): JSX.Element { - const { rootKey, taxonomicEntityFilterGroupTypes, taxonomicPropertyFilterGroupTypes } = - useValues(universalFiltersLogic) + const { rootKey, taxonomicGroupTypes } = useValues(universalFiltersLogic) const { replaceGroupValue } = useActions(universalFiltersLogic) return ( @@ -74,8 +70,7 @@ function Group({ rootKey={`${rootKey}.group_${index}`} group={group} onChange={(group) => replaceGroupValue(index, group)} - taxonomicEntityFilterGroupTypes={taxonomicEntityFilterGroupTypes} - taxonomicPropertyFilterGroupTypes={taxonomicPropertyFilterGroupTypes} + taxonomicGroupTypes={taxonomicGroupTypes} > {children} diff --git a/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.test.ts b/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.test.ts index 448647033b49c7..82b52b2e38053d 100644 --- a/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.test.ts +++ b/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.test.ts @@ -32,8 +32,9 @@ describe('universalFiltersLogic', () => { logic = universalFiltersLogic({ rootKey: 'test', group: defaultFilter, - taxonomicEntityFilterGroupTypes: [TaxonomicFilterGroupType.Events, TaxonomicFilterGroupType.Actions], - taxonomicPropertyFilterGroupTypes: [ + taxonomicGroupTypes: [ + TaxonomicFilterGroupType.Events, + TaxonomicFilterGroupType.Actions, TaxonomicFilterGroupType.EventProperties, TaxonomicFilterGroupType.PersonProperties, ], diff --git a/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.ts b/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.ts index f6242e3d674476..9f40b436c3e3a6 100644 --- a/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.ts +++ b/frontend/src/lib/components/UniversalFilters/universalFiltersLogic.ts @@ -6,7 +6,7 @@ import { import { taxonomicFilterGroupTypeToEntityType } from 'scenes/insights/filters/ActionFilter/ActionFilterRow/ActionFilterRow' import { propertyDefinitionsModel } from '~/models/propertyDefinitionsModel' -import { ActionFilter, FilterLogicalOperator } from '~/types' +import { ActionFilter, FilterLogicalOperator, PropertyFilterType } from '~/types' import { TaxonomicFilterGroup, TaxonomicFilterGroupType, TaxonomicFilterValue } from '../TaxonomicFilter/types' import { UniversalFiltersGroup, UniversalFiltersGroupValue } from './UniversalFilters' @@ -26,8 +26,7 @@ export type UniversalFiltersLogicProps = { rootKey: string group: UniversalFiltersGroup | null onChange: (group: UniversalFiltersGroup) => void - taxonomicEntityFilterGroupTypes: TaxonomicFilterGroupType[] - taxonomicPropertyFilterGroupTypes: TaxonomicFilterGroupType[] + taxonomicGroupTypes: TaxonomicFilterGroupType[] } export const universalFiltersLogic = kea([ @@ -50,7 +49,11 @@ export const universalFiltersLogic = kea([ }), removeGroupValue: (index: number) => ({ index }), - addGroupFilter: (taxonomicGroup: TaxonomicFilterGroup, propertyKey: TaxonomicFilterValue, item: any) => ({ + addGroupFilter: ( + taxonomicGroup: TaxonomicFilterGroup, + propertyKey: TaxonomicFilterValue, + item: { propertyFilterType?: PropertyFilterType; name?: string } + ) => ({ taxonomicGroup, propertyKey, item, @@ -83,11 +86,20 @@ export const universalFiltersLogic = kea([ selectors({ rootKey: [(_, p) => [p.rootKey], (rootKey) => rootKey], - taxonomicEntityFilterGroupTypes: [(_, p) => [p.taxonomicEntityFilterGroupTypes], (types) => types], - taxonomicPropertyFilterGroupTypes: [(_, p) => [p.taxonomicPropertyFilterGroupTypes], (types) => types], - taxonomicGroupTypes: [ - (_, p) => [p.taxonomicEntityFilterGroupTypes, p.taxonomicPropertyFilterGroupTypes], - (entityTypes, propertyTypes) => [...entityTypes, ...propertyTypes], + taxonomicGroupTypes: [(_, p) => [p.taxonomicGroupTypes], (types) => types], + taxonomicPropertyFilterGroupTypes: [ + (_, p) => [p.taxonomicGroupTypes], + (types) => + types.filter((t) => + [ + TaxonomicFilterGroupType.EventProperties, + TaxonomicFilterGroupType.PersonProperties, + TaxonomicFilterGroupType.EventFeatureFlags, + TaxonomicFilterGroupType.Cohorts, + TaxonomicFilterGroupType.Elements, + TaxonomicFilterGroupType.HogQLExpression, + ].includes(t) + ), ], }), @@ -112,7 +124,7 @@ export const universalFiltersLogic = kea([ newValues.push(newPropertyFilter) } else { - const entityType = item.PropertyFilterType ?? taxonomicFilterGroupTypeToEntityType(taxonomicGroup.type) + const entityType = taxonomicFilterGroupTypeToEntityType(taxonomicGroup.type) if (entityType) { const newEntityFilter: ActionFilter = { id: propertyKey, diff --git a/frontend/src/lib/taxonomy.tsx b/frontend/src/lib/taxonomy.tsx index 59abaaa055fca9..17fd94ac793dee 100644 --- a/frontend/src/lib/taxonomy.tsx +++ b/frontend/src/lib/taxonomy.tsx @@ -1075,6 +1075,17 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = { description: 'Specified group key', }, }, + replay: { + console_log_level: { + label: 'Log level', + description: 'Level of console logs captured', + examples: ['info', 'warn', 'error'], + }, + console_log_query: { + label: 'Console log', + description: 'Text of console logs captured', + }, + }, } satisfies Partial>> CORE_FILTER_DEFINITIONS_BY_GROUP.numerical_event_properties = CORE_FILTER_DEFINITIONS_BY_GROUP.event_properties diff --git a/frontend/src/models/propertyDefinitionsModel.ts b/frontend/src/models/propertyDefinitionsModel.ts index 497a218b3eaaf5..1e9a165e026141 100644 --- a/frontend/src/models/propertyDefinitionsModel.ts +++ b/frontend/src/models/propertyDefinitionsModel.ts @@ -55,6 +55,7 @@ export type Option = { label?: string name?: string status?: 'loading' | 'loaded' + allowCustomValues?: boolean values?: PropValue[] } @@ -149,7 +150,11 @@ export const propertyDefinitionsModel = kea([ eventNames?: string[] }) => payload, setOptionsLoading: (key: string) => ({ key }), - setOptions: (key: string, values: PropValue[]) => ({ key, values }), + setOptions: (key: string, values: PropValue[], allowCustomValues: boolean) => ({ + key, + values, + allowCustomValues, + }), // internal fetchAllPendingDefinitions: true, abortAnyRunningQuery: true, @@ -170,11 +175,12 @@ export const propertyDefinitionsModel = kea([ {} as Record, { setOptionsLoading: (state, { key }) => ({ ...state, [key]: { ...state[key], status: 'loading' } }), - setOptions: (state, { key, values }) => ({ + setOptions: (state, { key, values, allowCustomValues }) => ({ ...state, [key]: { values: [...Array.from(new Set(values))], status: 'loaded', + allowCustomValues, }, }), }, @@ -317,6 +323,19 @@ export const propertyDefinitionsModel = kea([ if (!propertyKey || values.currentTeamId === null) { return } + if (propertyKey === 'console_log_level') { + actions.setOptions( + propertyKey, + [ + // id is not used so can be arbitrarily chosen + { id: 0, name: 'info' }, + { id: 1, name: 'warn' }, + { id: 2, name: 'error' }, + ], + false + ) + return + } const start = performance.now() @@ -334,7 +353,7 @@ export const propertyDefinitionsModel = kea([ methodOptions ) breakpoint() - actions.setOptions(propertyKey, propValues) + actions.setOptions(propertyKey, propValues, true) cache.abortController = null await captureTimeToSeeData(teamLogic.values.currentTeamId, { diff --git a/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx b/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx index e60e2ccc569be9..2625549c1b53d0 100644 --- a/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx +++ b/frontend/src/scenes/session-recordings/filters/RecordingsUniversalFilters.tsx @@ -89,11 +89,10 @@ export const RecordingsUniversalFilters = (): JSX.Element => { void +} + +export function ReplayTaxonomicFilters({ onChange }: ReplayTaxonomicFiltersProps): JSX.Element { + const { + filterGroup: { values: filters }, + } = useValues(universalFiltersLogic) + + const hasConsoleLogLevelFilter = filters.find( + (f) => f.type === PropertyFilterType.Recording && f.key === 'console_log_level' + ) + const hasConsoleLogQueryFilter = filters.find( + (f) => f.type === PropertyFilterType.Recording && f.key === 'console_log_query' + ) + + return ( +
+
+
Session properties
+
    + onChange('console_log_level', {})} + disabledReason={hasConsoleLogLevelFilter ? 'Log level filter already added' : undefined} + > + Console log level + + onChange('console_log_query', {})} + disabledReason={hasConsoleLogQueryFilter ? 'Log text filter already added' : undefined} + > + Console log text + +
+
+ + +
+ ) +} + +const PersonProperties = ({ onChange }: { onChange: ReplayTaxonomicFiltersProps['onChange'] }): JSX.Element => { + const { quickFilterProperties: properties } = useValues(playerSettingsLogic) + const { setQuickFilterProperties } = useActions(playerSettingsLogic) + + const [showPropertySelector, setShowPropertySelector] = useState(false) + + return ( +
+
Person properties
+
    + {properties.map((property) => ( + { + const newProperties = properties.filter((p) => p != property) + setQuickFilterProperties(newProperties) + }, + icon: , + }} + onClick={() => onChange(property, { propertyFilterType: PropertyFilterType.Person })} + > + + + ))} + setShowPropertySelector(false)} + placement="right-start" + overlay={ + { + properties.push(value as string) + setQuickFilterProperties([...properties]) + setShowPropertySelector(false) + }} + taxonomicGroupTypes={[TaxonomicFilterGroupType.PersonProperties]} + excludedProperties={{ [TaxonomicFilterGroupType.PersonProperties]: properties }} + /> + } + > + setShowPropertySelector(!showPropertySelector)} fullWidth> + Add property + + +
+
+ ) +} diff --git a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts index 65829d1257afdf..965e6f33382e3f 100644 --- a/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts +++ b/frontend/src/scenes/session-recordings/player/playerSettingsLogic.ts @@ -1,5 +1,6 @@ -import { actions, kea, listeners, path, reducers, selectors } from 'kea' +import { actions, connect, kea, listeners, path, reducers, selectors } from 'kea' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' +import { teamLogic } from 'scenes/teamLogic' import { AutoplayDirection, DurationType, SessionRecordingPlayerTab } from '~/types' @@ -191,7 +192,10 @@ export const playerSettingsLogic = kea([ setQuickFilterProperties: (properties: string[]) => ({ properties }), setTimestampFormat: (format: TimestampFormat) => ({ format }), }), - reducers(() => ({ + connect({ + values: [teamLogic, ['currentTeam']], + }), + reducers(({ values }) => ({ showFilters: [ true, { @@ -211,7 +215,7 @@ export const playerSettingsLogic = kea([ }, ], quickFilterProperties: [ - ['$geoip_country_name'] as string[], + ['$geoip_country_name', ...(values.currentTeam?.person_display_name_properties || [])] as string[], { persist: true, }, diff --git a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts index 122c80c77de275..7d8b34203a4033 100644 --- a/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts +++ b/frontend/src/scenes/session-recordings/playlist/sessionRecordingsPlaylistLogic.ts @@ -18,6 +18,7 @@ import posthog from 'posthog-js' import { AnyPropertyFilter, DurationType, + FilterableLogLevel, FilterType, PropertyFilterType, PropertyOperator, @@ -128,6 +129,8 @@ function convertUniversalFiltersToLegacyFilters(universalFilters: RecordingUnive const properties: AnyPropertyFilter[] = [] const events: FilterType['events'] = [] const actions: FilterType['actions'] = [] + let console_logs: FilterableLogLevel[] = [] + let console_search_query = '' filters.forEach((f) => { if (isEventFilter(f)) { @@ -136,7 +139,11 @@ function convertUniversalFiltersToLegacyFilters(universalFilters: RecordingUnive actions.push(f) } else if (isAnyPropertyfilter(f)) { if (f.type === PropertyFilterType.Recording) { - // TODO: add console log filtering + if (f.key === 'console_log_level') { + console_logs = f.value as FilterableLogLevel[] + } else if (f.key === 'console_log_query') { + console_search_query = (f.value || '') as string + } } else { properties.push(f) } @@ -152,6 +159,8 @@ function convertUniversalFiltersToLegacyFilters(universalFilters: RecordingUnive actions, session_recording_duration: { ...durationFilter, key: 'duration' }, duration_type_filter: durationFilter.key, + console_search_query, + console_logs, } } From fda2156084014112123f4b979eba1102cf3db6b2 Mon Sep 17 00:00:00 2001 From: David Newell Date: Tue, 11 Jun 2024 14:59:23 +0100 Subject: [PATCH 35/35] feat: Error tracking (simple start) (#22814) --- .../navigation-3000/navigationLogic.tsx | 9 + .../CommandPalette/commandPaletteLogic.tsx | 12 ++ .../Errors/ErrorDisplay.stories.tsx | 177 +++++++++--------- .../lib/components/Errors/ErrorDisplay.tsx | 10 +- frontend/src/lib/constants.tsx | 1 + .../scenes/activity/explore/EventDetails.tsx | 2 +- frontend/src/scenes/appScenes.ts | 1 + .../error-tracking/ErrorTrackingScene.tsx | 46 +++++ .../error-tracking/errorTrackingSceneLogic.ts | 47 +++++ frontend/src/scenes/sceneTypes.ts | 1 + frontend/src/scenes/scenes.ts | 5 + .../player/inspector/components/ItemEvent.tsx | 2 +- frontend/src/scenes/urls.ts | 2 + frontend/src/types.ts | 7 + 14 files changed, 220 insertions(+), 102 deletions(-) create mode 100644 frontend/src/scenes/error-tracking/ErrorTrackingScene.tsx create mode 100644 frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts diff --git a/frontend/src/layout/navigation-3000/navigationLogic.tsx b/frontend/src/layout/navigation-3000/navigationLogic.tsx index af344d75ed1767..b09cc1a3f69480 100644 --- a/frontend/src/layout/navigation-3000/navigationLogic.tsx +++ b/frontend/src/layout/navigation-3000/navigationLogic.tsx @@ -17,6 +17,7 @@ import { IconServer, IconTestTube, IconToggle, + IconWarning, } from '@posthog/icons' import { lemonToast, Spinner } from '@posthog/lemon-ui' import { captureException } from '@sentry/react' @@ -450,6 +451,14 @@ export const navigation3000Logic = kea([ icon: , to: urls.replay(), }, + featureFlags[FEATURE_FLAGS.ERROR_TRACKING] + ? { + identifier: Scene.ErrorTracking, + label: 'Error tracking', + icon: , + to: urls.errorTracking(), + } + : null, featureFlags[FEATURE_FLAGS.HEATMAPS_UI] ? { identifier: Scene.Heatmaps, diff --git a/frontend/src/lib/components/CommandPalette/commandPaletteLogic.tsx b/frontend/src/lib/components/CommandPalette/commandPaletteLogic.tsx index 77e9e2c33a701b..8084a2a07d650e 100644 --- a/frontend/src/lib/components/CommandPalette/commandPaletteLogic.tsx +++ b/frontend/src/lib/components/CommandPalette/commandPaletteLogic.tsx @@ -38,6 +38,7 @@ import { IconTrends, IconUnlock, IconUserPaths, + IconWarning, IconX, } from '@posthog/icons' import { Parser } from 'expr-eval' @@ -581,6 +582,17 @@ export const commandPaletteLogic = kea([ }, ] : []), + ...(values.featureFlags[FEATURE_FLAGS.ERROR_TRACKING] + ? [ + { + icon: IconWarning, + display: 'Go to Error tracking', + executor: () => { + push(urls.errorTracking()) + }, + }, + ] + : []), { display: 'Go to Session replay', icon: IconRewindPlay, diff --git a/frontend/src/lib/components/Errors/ErrorDisplay.stories.tsx b/frontend/src/lib/components/Errors/ErrorDisplay.stories.tsx index 86915996ae5f68..b0aa04cc278000 100644 --- a/frontend/src/lib/components/Errors/ErrorDisplay.stories.tsx +++ b/frontend/src/lib/components/Errors/ErrorDisplay.stories.tsx @@ -1,7 +1,7 @@ import { Meta } from '@storybook/react' import { ErrorDisplay } from 'lib/components/Errors/ErrorDisplay' -import { EventType, RecordingEventType } from '~/types' +import { EventType } from '~/types' const meta: Meta = { title: 'Components/Errors/Error Display', @@ -9,104 +9,95 @@ const meta: Meta = { } export default meta -function errorEvent(properties: Record): EventType | RecordingEventType { +function errorProperties(properties: Record): EventType['properties'] { return { - id: '12345', - elements: [], - uuid: '018880b6-b781-0008-a2e5-629b2624fd2f', - event: '$exception', - properties: { - $os: 'Windows', - $os_version: '10.0', - $browser: 'Chrome', - $device_type: 'Desktop', - $current_url: 'https://app.posthog.com/home', - $host: 'app.posthog.com', - $pathname: '/home', - $browser_version: 113, - $browser_language: 'es-ES', - $screen_height: 1080, - $screen_width: 1920, - $viewport_height: 929, - $viewport_width: 1920, - $lib: 'web', - $lib_version: '1.63.3', - distinct_id: 'iOizUPH4RH65nZjvGVBz5zZUmwdHvq2mxzNySQqqYkG', - $device_id: '186144e7357245-0cfe8bf1b5b877-26021051-1fa400-186144e7358d3', - $active_feature_flags: ['are-the-flags', 'important-for-the-error'], - $feature_flag_payloads: { - 'are-the-flags': '{\n "flag": "payload"\n}', - }, - $user_id: 'iOizUPH4RH65nZjvGVBz5zZUmwdHvq2mxzNySQqqYkG', - $groups: { - project: '00000000-0000-0000-1847-88f0ffa23444', - organization: '00000000-0000-0000-a050-5d4557279956', - customer: 'the-customer', - instance: 'https://app.posthog.com', - }, - $exception_message: 'ResizeObserver loop limit exceeded', - $exception_type: 'Error', - $exception_personURL: 'https://app.posthog.com/person/the-person-id', - $sentry_event_id: 'id-from-the-sentry-integration', - $sentry_exception: { - values: [ - { - value: 'ResizeObserver loop limit exceeded', - type: 'Error', - mechanism: { - type: 'onerror', - handled: false, - synthetic: true, - }, - stacktrace: { - frames: [ - { - colno: 0, - filename: 'https://app.posthog.com/home', - function: '?', - in_app: true, - lineno: 0, - }, - ], - }, + $os: 'Windows', + $os_version: '10.0', + $browser: 'Chrome', + $device_type: 'Desktop', + $current_url: 'https://app.posthog.com/home', + $host: 'app.posthog.com', + $pathname: '/home', + $browser_version: 113, + $browser_language: 'es-ES', + $screen_height: 1080, + $screen_width: 1920, + $viewport_height: 929, + $viewport_width: 1920, + $lib: 'web', + $lib_version: '1.63.3', + distinct_id: 'iOizUPH4RH65nZjvGVBz5zZUmwdHvq2mxzNySQqqYkG', + $device_id: '186144e7357245-0cfe8bf1b5b877-26021051-1fa400-186144e7358d3', + $active_feature_flags: ['are-the-flags', 'important-for-the-error'], + $feature_flag_payloads: { + 'are-the-flags': '{\n "flag": "payload"\n}', + }, + $user_id: 'iOizUPH4RH65nZjvGVBz5zZUmwdHvq2mxzNySQqqYkG', + $groups: { + project: '00000000-0000-0000-1847-88f0ffa23444', + organization: '00000000-0000-0000-a050-5d4557279956', + customer: 'the-customer', + instance: 'https://app.posthog.com', + }, + $exception_message: 'ResizeObserver loop limit exceeded', + $exception_type: 'Error', + $exception_personURL: 'https://app.posthog.com/person/the-person-id', + $sentry_event_id: 'id-from-the-sentry-integration', + $sentry_exception: { + values: [ + { + value: 'ResizeObserver loop limit exceeded', + type: 'Error', + mechanism: { + type: 'onerror', + handled: false, + synthetic: true, }, - ], - }, - $sentry_exception_message: 'ResizeObserver loop limit exceeded', - $sentry_exception_type: 'Error', - $sentry_tags: { - 'PostHog Person URL': 'https://app.posthog.com/person/the-person-id', - 'PostHog Recording URL': 'https://app.posthog.com/replay/the-session-id?t=866', - }, - $sentry_url: - 'https://sentry.io/organizations/posthog/issues/?project=the-sentry-project-id&query=the-sentry-id', - $session_id: 'the-session-id', - $window_id: 'the-window-id', - $pageview_id: 'the-pageview-id', - $sent_at: '2023-06-03T10:03:57.787000+00:00', - $geoip_city_name: 'Whoville', - $geoip_country_name: 'Wholand', - $geoip_country_code: 'WH', - $geoip_continent_name: 'Mystery', - $geoip_continent_code: 'MY', - $geoip_latitude: -30.5023, - $geoip_longitude: -71.1545, - $geoip_time_zone: 'UTC', - $lib_version__major: 1, - $lib_version__minor: 63, - $lib_version__patch: 3, - ...properties, + stacktrace: { + frames: [ + { + colno: 0, + filename: 'https://app.posthog.com/home', + function: '?', + in_app: true, + lineno: 0, + }, + ], + }, + }, + ], + }, + $sentry_exception_message: 'ResizeObserver loop limit exceeded', + $sentry_exception_type: 'Error', + $sentry_tags: { + 'PostHog Person URL': 'https://app.posthog.com/person/the-person-id', + 'PostHog Recording URL': 'https://app.posthog.com/replay/the-session-id?t=866', }, - timestamp: '2023-06-03T03:03:57.316-07:00', - distinct_id: 'the-distinct-id', - elements_chain: '', + $sentry_url: + 'https://sentry.io/organizations/posthog/issues/?project=the-sentry-project-id&query=the-sentry-id', + $session_id: 'the-session-id', + $window_id: 'the-window-id', + $pageview_id: 'the-pageview-id', + $sent_at: '2023-06-03T10:03:57.787000+00:00', + $geoip_city_name: 'Whoville', + $geoip_country_name: 'Wholand', + $geoip_country_code: 'WH', + $geoip_continent_name: 'Mystery', + $geoip_continent_code: 'MY', + $geoip_latitude: -30.5023, + $geoip_longitude: -71.1545, + $geoip_time_zone: 'UTC', + $lib_version__major: 1, + $lib_version__minor: 63, + $lib_version__patch: 3, + ...properties, } } export function ResizeObserverLoopLimitExceeded(): JSX.Element { return ( ) } } -export function ErrorDisplay({ event }: { event: EventType | RecordingEventType }): JSX.Element { - if (event.event !== '$exception') { - return <>Unknown type of error - } - +export function ErrorDisplay({ eventProperties }: { eventProperties: EventType['properties'] }): JSX.Element { const { $exception_type, $exception_message, @@ -175,7 +171,7 @@ export function ErrorDisplay({ event }: { event: EventType | RecordingEventType $sentry_url, $exception_stack_trace_raw, $level, - } = getExceptionPropertiesFrom(event.properties) + } = getExceptionPropertiesFrom(eventProperties) return (
diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index 47903d65943cf7..07e8c6d51b86f0 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -210,6 +210,7 @@ export const FEATURE_FLAGS = { PERSONLESS_EVENTS_NOT_SUPPORTED: 'personless-events-not-supported', // owner: @raquelmsmith SESSION_REPLAY_UNIVERSAL_FILTERS: 'session-replay-universal-filters', // owner: #team-replay ALERTS: 'alerts', // owner: github.com/nikitaevg + ERROR_TRACKING: 'error-tracking', // owner: #team-replay SETTINGS_BOUNCE_RATE_PAGE_VIEW_MODE: 'settings-bounce-rate-page-view-mode', // owner: @robbie-c SURVEYS_BRANCHING_LOGIC: 'surveys-branching-logic', // owner: @jurajmajerik #team-feature-success } as const diff --git a/frontend/src/scenes/activity/explore/EventDetails.tsx b/frontend/src/scenes/activity/explore/EventDetails.tsx index e512838f0020af..8226a7289e2661 100644 --- a/frontend/src/scenes/activity/explore/EventDetails.tsx +++ b/frontend/src/scenes/activity/explore/EventDetails.tsx @@ -93,7 +93,7 @@ export function EventDetails({ event, tableProps }: EventDetailsProps): JSX.Elem label: 'Exception', content: (
- +
), }) diff --git a/frontend/src/scenes/appScenes.ts b/frontend/src/scenes/appScenes.ts index 07da32a0541609..01903f190d5c27 100644 --- a/frontend/src/scenes/appScenes.ts +++ b/frontend/src/scenes/appScenes.ts @@ -37,6 +37,7 @@ export const appScenes: Record any> = { [Scene.FeatureFlag]: () => import('./feature-flags/FeatureFlag'), [Scene.EarlyAccessFeatures]: () => import('./early-access-features/EarlyAccessFeatures'), [Scene.EarlyAccessFeature]: () => import('./early-access-features/EarlyAccessFeature'), + [Scene.ErrorTracking]: () => import('./error-tracking/ErrorTrackingScene'), [Scene.Surveys]: () => import('./surveys/Surveys'), [Scene.Survey]: () => import('./surveys/Survey'), [Scene.SurveyTemplates]: () => import('./surveys/SurveyTemplates'), diff --git a/frontend/src/scenes/error-tracking/ErrorTrackingScene.tsx b/frontend/src/scenes/error-tracking/ErrorTrackingScene.tsx new file mode 100644 index 00000000000000..2baa8584c8d6a4 --- /dev/null +++ b/frontend/src/scenes/error-tracking/ErrorTrackingScene.tsx @@ -0,0 +1,46 @@ +import { LemonTable } from '@posthog/lemon-ui' +import { useValues } from 'kea' +import { ErrorDisplay } from 'lib/components/Errors/ErrorDisplay' +import { SceneExport } from 'scenes/sceneTypes' + +import { ErrorTrackingGroup } from '~/types' + +import { errorTrackingSceneLogic } from './errorTrackingSceneLogic' + +export const scene: SceneExport = { + component: ErrorTrackingScene, + logic: errorTrackingSceneLogic, +} + +export function ErrorTrackingScene(): JSX.Element { + const { errorGroups, errorGroupsLoading } = useValues(errorTrackingSceneLogic) + + return ( + a.occurrences - b.occurrences, + }, + { + title: 'Sessions', + dataIndex: 'uniqueSessions', + sorter: (a, b) => a.uniqueSessions - b.uniqueSessions, + }, + ]} + loading={errorGroupsLoading} + dataSource={errorGroups} + expandable={{ + expandedRowRender: function renderExpand(group: ErrorTrackingGroup) { + return + }, + noIndent: true, + }} + /> + ) +} diff --git a/frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts b/frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts new file mode 100644 index 00000000000000..8cca4639c94d03 --- /dev/null +++ b/frontend/src/scenes/error-tracking/errorTrackingSceneLogic.ts @@ -0,0 +1,47 @@ +import { afterMount, kea, path } from 'kea' +import { loaders } from 'kea-loaders' +import api from 'lib/api' + +import { HogQLQuery, NodeKind } from '~/queries/schema' +import { hogql } from '~/queries/utils' +import { ErrorTrackingGroup } from '~/types' + +import type { errorTrackingSceneLogicType } from './errorTrackingSceneLogicType' + +export const errorTrackingSceneLogic = kea([ + path(['scenes', 'error-tracking', 'errorTrackingSceneLogic']), + + loaders(() => ({ + errorGroups: [ + [] as ErrorTrackingGroup[], + { + loadErrorGroups: async () => { + const query: HogQLQuery = { + kind: NodeKind.HogQLQuery, + query: hogql`SELECT first_value(properties), count(), count(distinct properties.$session_id) + FROM events e + WHERE event = '$exception' + -- grouping by message for now, will eventually be predefined $exception_group_id + GROUP BY properties.$exception_message`, + } + + const res = await api.query(query) + + return res.results.map((r) => { + const eventProperties = JSON.parse(r[0]) + return { + title: eventProperties['$exception_message'] || 'No message', + sampleEventProperties: eventProperties, + occurrences: r[2], + uniqueSessions: r[3], + } + }) + }, + }, + ], + })), + + afterMount(({ actions }) => { + actions.loadErrorGroups() + }), +]) diff --git a/frontend/src/scenes/sceneTypes.ts b/frontend/src/scenes/sceneTypes.ts index c80897e8dd192d..1dcb3f8af312bb 100644 --- a/frontend/src/scenes/sceneTypes.ts +++ b/frontend/src/scenes/sceneTypes.ts @@ -9,6 +9,7 @@ export enum Scene { Error404 = '404', ErrorNetwork = '4xx', ErrorProjectUnavailable = 'ProjectUnavailable', + ErrorTracking = 'ErrorTracking', Dashboards = 'Dashboards', Dashboard = 'Dashboard', Insight = 'Insight', diff --git a/frontend/src/scenes/scenes.ts b/frontend/src/scenes/scenes.ts index 7d44fe0927b7e1..f4ef644d8665c7 100644 --- a/frontend/src/scenes/scenes.ts +++ b/frontend/src/scenes/scenes.ts @@ -53,6 +53,10 @@ export const sceneConfigurations: Record = { activityScope: ActivityScope.DASHBOARD, defaultDocsPath: '/docs/product-analytics/dashboards', }, + [Scene.ErrorTracking]: { + projectBased: true, + name: 'Error tracking', + }, [Scene.Insight]: { projectBased: true, name: 'Insights', @@ -540,6 +544,7 @@ export const routes: Record = { [urls.experiment(':id')]: Scene.Experiment, [urls.earlyAccessFeatures()]: Scene.EarlyAccessFeatures, [urls.earlyAccessFeature(':id')]: Scene.EarlyAccessFeature, + [urls.errorTracking()]: Scene.ErrorTracking, [urls.surveys()]: Scene.Surveys, [urls.survey(':id')]: Scene.Survey, [urls.surveyTemplates()]: Scene.SurveyTemplates, diff --git a/frontend/src/scenes/session-recordings/player/inspector/components/ItemEvent.tsx b/frontend/src/scenes/session-recordings/player/inspector/components/ItemEvent.tsx index e851684d588745..9131ba82271d2c 100644 --- a/frontend/src/scenes/session-recordings/player/inspector/components/ItemEvent.tsx +++ b/frontend/src/scenes/session-recordings/player/inspector/components/ItemEvent.tsx @@ -68,7 +68,7 @@ export function ItemEvent({ item, expanded, setExpanded }: ItemEventProps): JSX. {item.data.fullyLoaded ? ( item.data.event === '$exception' ? ( - + ) : ( ) diff --git a/frontend/src/scenes/urls.ts b/frontend/src/scenes/urls.ts index a06f49aeeb5b60..c5c68db5b337cd 100644 --- a/frontend/src/scenes/urls.ts +++ b/frontend/src/scenes/urls.ts @@ -149,6 +149,8 @@ export const urls = { earlyAccessFeatures: (): string => '/early_access_features', /** @param id A UUID or 'new'. ':id' for routing. */ earlyAccessFeature: (id: string): string => `/early_access_features/${id}`, + errorTracking: (): string => '/error_tracking', + errorTrackingGroup: (id: string): string => `/error_tracking/${id}`, surveys: (): string => '/surveys', /** @param id A UUID or 'new'. ':id' for routing. */ survey: (id: string): string => `/surveys/${id}`, diff --git a/frontend/src/types.ts b/frontend/src/types.ts index d9ba47ab428d45..160a46df07749f 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -1006,6 +1006,13 @@ export type ErrorCluster = { } export type ErrorClusterResponse = ErrorCluster[] | null +export type ErrorTrackingGroup = { + title: string + sampleEventProperties: EventType['properties'] + occurrences: number + uniqueSessions: number +} + export type EntityType = 'actions' | 'events' | 'data_warehouse' | 'new_entity' export interface Entity {