Skip to content

Commit

Permalink
Merge branch 'master' into feature/alerts-config-check
Browse files Browse the repository at this point in the history
  • Loading branch information
webjunkie committed Aug 21, 2024
2 parents 2df358c + aa57fb6 commit bf50012
Show file tree
Hide file tree
Showing 47 changed files with 349 additions and 116 deletions.
85 changes: 85 additions & 0 deletions cypress/e2e/billing-limits.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
describe('Billing Limits', () => {
it('Show no limits set and allow user to set one', () => {
cy.intercept('GET', '/api/billing/', { fixture: 'api/billing/billing.json' }).as('getBilling')
cy.visit('/organization/billing')
cy.wait('@getBilling')

cy.intercept('PATCH', '/api/billing/', (req) => {
req.reply({
statusCode: 200,
body: {
...require('../fixtures/api/billing/billing.json'),
custom_limits_usd: { product_analytics: 100 },
},
})
}).as('patchBilling')

cy.get('[data-attr="billing-limit-input-wrapper-product_analytics"]').scrollIntoView()
cy.get('[data-attr="billing-limit-not-set-product_analytics"]').should('be.visible')
cy.contains('Set a billing limit').click()
cy.get('[data-attr="billing-limit-input-product_analytics"]').clear().type('100')
cy.get('[data-attr="save-billing-limit-product_analytics"]').click()
cy.wait('@patchBilling')
cy.get('[data-attr="billing-limit-set-product_analytics"]').should(
'contain',
'You have a $100 billing limit set'
)
})

it('Show existing limit and allow user to change it', () => {
cy.intercept('GET', '/api/billing/', (req) => {
req.reply({
statusCode: 200,
body: {
...require('../fixtures/api/billing/billing.json'),
custom_limits_usd: { product_analytics: 100 },
},
})
}).as('getBilling')
cy.visit('/organization/billing')
cy.wait('@getBilling')

cy.intercept('PATCH', '/api/billing/', (req) => {
req.reply({
statusCode: 200,
body: {
...require('../fixtures/api/billing/billing.json'),
custom_limits_usd: { product_analytics: 200 },
},
})
}).as('patchBilling')

cy.get('[data-attr="billing-limit-input-wrapper-product_analytics"]').scrollIntoView()
cy.get('[data-attr="billing-limit-set-product_analytics"]').should('be.visible')
cy.contains('Edit limit').click()
cy.get('[data-attr="billing-limit-input-product_analytics"]').clear().type('200')
cy.get('[data-attr="save-billing-limit-product_analytics"]').click()
cy.wait('@patchBilling')
cy.get('[data-attr="billing-limit-set-product_analytics"]').should(
'contain',
'You have a $200 billing limit set'
)
})

it('Show existing limit and allow user to remove it', () => {
cy.intercept('GET', '/api/billing/', (req) => {
req.reply({
statusCode: 200,
body: {
...require('../fixtures/api/billing/billing.json'),
custom_limits_usd: { product_analytics: 100 },
},
})
}).as('getBilling')
cy.visit('/organization/billing')
cy.wait('@getBilling')

cy.intercept('PATCH', '/api/billing/', { fixture: 'api/billing/billing.json' }).as('patchBilling')

cy.get('[data-attr="billing-limit-input-wrapper-product_analytics"]').scrollIntoView()
cy.get('[data-attr="billing-limit-set-product_analytics"]').should('be.visible')
cy.contains('Edit limit').click()
cy.get('[data-attr="remove-billing-limit-product_analytics"]').click()
cy.get('[data-attr="billing-limit-not-set-product_analytics"]').should('be.visible')
})
})
2 changes: 1 addition & 1 deletion ee/billing/billing_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class CustomerInfo(TypedDict):
current_total_amount_usd: Optional[str]
current_total_amount_usd_after_discount: Optional[str]
products: Optional[list[CustomerProduct]]
custom_limits_usd: Optional[dict[str, str | int]]
custom_limits_usd: Optional[dict[str, int]]
usage_summary: Optional[dict[str, dict[str, Optional[int]]]]
free_trial_until: Optional[str]
discount_percent: Optional[int]
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-dashboards--edit--light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-dashboards--show--light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ export const FEATURE_FLAGS = {
PERSON_BATCH_EXPORTS: 'person-batch-exports', // owner: @tomasfarias
// owner: #team-replay, only to be enabled for PostHog team testing
EXCEPTION_AUTOCAPTURE: 'exception-autocapture',
WEB_VITALS_AUTOCAPTURE: 'web-vitals-autocapture', // owner: @team-replay
FF_DASHBOARD_TEMPLATES: 'ff-dashboard-templates', // owner: @EDsCODE
ARTIFICIAL_HOG: 'artificial-hog', // owner: @Twixes
CS_DASHBOARDS: 'cs-dashboards', // owner: @pauldambra
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/queries/Query/Query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface QueryProps<Q extends Node> {
setQuery?: (query: Q, isSourceUpdate?: boolean) => void

/** Custom components passed down to a few query nodes (e.g. custom table columns) */
context?: QueryContext
context?: QueryContext<any>
/* Cached Results are provided when shared or exported,
the data node logic becomes read only implicitly */
cachedResults?: AnyResponseType
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/queries/nodes/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ interface DataTableProps {
query: DataTableNode
setQuery?: (query: DataTableNode) => void
/** Custom table columns */
context?: QueryContext
context?: QueryContext<DataTableNode>
/* Cached Results are provided when shared or exported,
the data node logic becomes read only implicitly */
cachedResults?: AnyResponseType
Expand All @@ -95,7 +95,7 @@ let uniqueNode = 0
export function DataTable({ uniqueKey, query, setQuery, context, cachedResults }: DataTableProps): JSX.Element {
const [uniqueNodeKey] = useState(() => uniqueNode++)
const [dataKey] = useState(() => `DataNode.${uniqueKey || uniqueNodeKey}`)
const insightProps: InsightLogicProps = context?.insightProps || {
const insightProps: InsightLogicProps<DataTableNode> = context?.insightProps || {
dashboardItemId: `new-AdHoc.${dataKey}`,
dataNodeCollectionId: dataKey,
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/queries/nodes/DataTable/dataTableLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface DataTableLogicProps {
vizKey: string
dataKey: string
query: DataTableNode
context?: QueryContext
context?: QueryContext<DataTableNode>
// Override the data logic node key if needed
dataNodeLogicKey?: string
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/queries/nodes/DataTable/renderColumn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function renderColumn(
recordIndex: number,
query: DataTableNode,
setQuery?: (query: DataTableNode) => void,
context?: QueryContext
context?: QueryContext<DataTableNode>
): JSX.Element | string {
const queryContextColumnName = key.startsWith('context.columns.') ? trimQuotes(key.substring(16)) : undefined
const queryContextColumn = queryContextColumnName ? context?.columns?.[queryContextColumnName] : undefined
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/queries/nodes/DataTable/renderColumnMeta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface ColumnMeta {
align?: 'left' | 'right' | 'center'
}

export function renderColumnMeta(key: string, query: DataTableNode, context?: QueryContext): ColumnMeta {
export function renderColumnMeta(key: string, query: DataTableNode, context?: QueryContext<DataTableNode>): ColumnMeta {
let width: string | number | undefined
let title: JSX.Element | string | undefined
const queryFeatures = getQueryFeatures(query.source)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { dataVisualizationLogic } from '../dataVisualizationLogic'
interface TableProps {
query: DataVisualizationNode
uniqueKey: string | number | undefined
context: QueryContext | undefined
context: QueryContext<DataVisualizationNode> | undefined
cachedResults: HogQLQueryResponse | undefined
}

Expand Down
52 changes: 34 additions & 18 deletions frontend/src/queries/nodes/DataVisualization/DataVisualization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ import { AnimationType } from 'lib/animations/animations'
import { Animation } from 'lib/components/Animation/Animation'
import { useCallback, useState } from 'react'
import { DatabaseTableTreeWithItems } from 'scenes/data-warehouse/external/DataWarehouseTables'
import { insightLogic } from 'scenes/insights/insightLogic'
import { HogQLBoldNumber } from 'scenes/insights/views/BoldNumber/BoldNumber'
import { urls } from 'scenes/urls'

import { insightVizDataCollectionId, insightVizDataNodeKey } from '~/queries/nodes/InsightViz/InsightViz'
import { AnyResponseType, DataVisualizationNode, HogQLQuery, HogQLQueryResponse, NodeKind } from '~/queries/schema'
import { QueryContext } from '~/queries/types'
import { ChartDisplayType } from '~/types'
import { ChartDisplayType, InsightLogicProps } from '~/types'

import { dataNodeLogic, DataNodeLogicProps } from '../DataNode/dataNodeLogic'
import { DateRange } from '../DataNode/DateRange'
Expand All @@ -35,7 +34,7 @@ interface DataTableVisualizationProps {
uniqueKey?: string | number
query: DataVisualizationNode
setQuery?: (query: DataVisualizationNode) => void
context?: QueryContext
context?: QueryContext<DataVisualizationNode>
/* Cached Results are provided when shared or exported,
the data node logic becomes read only implicitly */
cachedResults?: AnyResponseType
Expand All @@ -44,34 +43,51 @@ interface DataTableVisualizationProps {

let uniqueNode = 0

export function DataTableVisualization(props: DataTableVisualizationProps): JSX.Element {
const [uniqueNodeKey] = useState(() => uniqueNode++)
const [key] = useState(`DataVisualizationNode.${props.uniqueKey?.toString() ?? uniqueNodeKey}`)

const { insightProps: insightLogicProps } = useValues(insightLogic)
export function DataTableVisualization({
uniqueKey,
query,
setQuery,
context,
cachedResults,
readOnly,
}: DataTableVisualizationProps): JSX.Element {
const [key] = useState(`DataVisualizationNode.${uniqueKey ?? uniqueNode++}`)
const insightProps: InsightLogicProps<DataVisualizationNode> = context?.insightProps || {
dashboardItemId: `new-AdHoc.${key}`,
query,
setQuery,
dataNodeCollectionId: key,
}

const vizKey = insightVizDataNodeKey(insightLogicProps)
const vizKey = insightVizDataNodeKey(insightProps)
const dataVisualizationLogicProps: DataVisualizationLogicProps = {
key: vizKey,
query: props.query,
insightLogicProps,
setQuery: props.setQuery,
cachedResults: props.cachedResults,
query,
insightLogicProps: insightProps,
setQuery,
cachedResults,
}

const dataNodeLogicProps: DataNodeLogicProps = {
query: props.query.source,
query: query.source,
key: vizKey,
cachedResults: props.cachedResults,
loadPriority: insightLogicProps.loadPriority,
dataNodeCollectionId: insightVizDataCollectionId(insightLogicProps, key),
cachedResults,
loadPriority: insightProps.loadPriority,
dataNodeCollectionId: insightVizDataCollectionId(insightProps, key),
}

return (
<BindLogic logic={dataNodeLogic} props={dataNodeLogicProps}>
<BindLogic logic={dataVisualizationLogic} props={dataVisualizationLogicProps}>
<BindLogic logic={displayLogic} props={{ key: dataVisualizationLogicProps.key }}>
<InternalDataTableVisualization {...props} uniqueKey={key} />
<InternalDataTableVisualization
uniqueKey={key}
query={query}
setQuery={setQuery}
context={context}
cachedResults={cachedResults}
readOnly={readOnly}
/>
</BindLogic>
</BindLogic>
</BindLogic>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ export interface AxisSeries<T> {
export interface DataVisualizationLogicProps {
key: string
query: DataVisualizationNode
insightLogicProps: InsightLogicProps
context?: QueryContext
setQuery?: (node: DataVisualizationNode) => void
insightLogicProps: InsightLogicProps<DataVisualizationNode>
context?: QueryContext<DataVisualizationNode>
cachedResults?: AnyResponseType
}

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/queries/nodes/InsightViz/InsightViz.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import { InsightVizDisplay } from './InsightVizDisplay'
import { getCachedResults } from './utils'

/** The key for the dataNodeLogic mounted by an InsightViz for insight of insightProps */
export const insightVizDataNodeKey = (insightProps: InsightLogicProps): string => {
export const insightVizDataNodeKey = (insightProps: InsightLogicProps<any>): string => {
return `InsightViz.${keyForInsightLogicProps('new')(insightProps)}`
}

export const insightVizDataCollectionId = (props: InsightLogicProps | undefined, fallback: string): string => {
export const insightVizDataCollectionId = (props: InsightLogicProps<any> | undefined, fallback: string): string => {
return props?.dataNodeCollectionId ?? props?.dashboardId?.toString() ?? props?.dashboardItemId ?? fallback
}

Expand Down
6 changes: 3 additions & 3 deletions frontend/src/queries/types.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { ComponentType, HTMLProps } from 'react'

import { QueryFeature } from '~/queries/nodes/DataTable/queryFeatures'
import { DataTableNode } from '~/queries/schema'
import { DataTableNode, InsightVizNode } from '~/queries/schema'
import { ChartDisplayType, GraphPointPayload, InsightLogicProps, TrendResult } from '~/types'

/** Pass custom metadata to queries. Used for e.g. custom columns in the DataTable. */
export interface QueryContext {
export interface QueryContext<T = InsightVizNode> {
/** Column templates for the DataTable */
columns?: Record<string, QueryContextColumn>
/** used to override the value in the query */
showOpenEditorButton?: boolean
showQueryEditor?: boolean
/* Adds help and examples to the query editor component */
showQueryHelp?: boolean
insightProps?: InsightLogicProps
insightProps?: InsightLogicProps<T>
emptyStateHeading?: string
emptyStateDetail?: string
rowProps?: (record: unknown) => Omit<HTMLProps<HTMLTableRowElement>, 'key'>
Expand Down
30 changes: 20 additions & 10 deletions frontend/src/scenes/billing/BillingLimit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { billingProductLogic } from './billingProductLogic'
export const BillingLimit = ({ product }: { product: BillingProductV2Type }): JSX.Element | null => {
const limitInputRef = useRef<HTMLInputElement | null>(null)
const { billing, billingLoading } = useValues(billingLogic)
const { isEditingBillingLimit, customLimitUsd, currentAndUpgradePlans } = useValues(
const { isEditingBillingLimit, customLimitUsd, hasCustomLimitSet, currentAndUpgradePlans } = useValues(
billingProductLogic({ product, billingLimitInputRef: limitInputRef })
)
const { setIsEditingBillingLimit, setBillingLimitInput, submitBillingLimitInput } = useActions(
Expand All @@ -26,26 +26,28 @@ export const BillingLimit = ({ product }: { product: BillingProductV2Type }): JS
return null
}

const hasCustomLimit = customLimitUsd === 0 || customLimitUsd
return (
<Form formKey="billingLimitInput" props={{ product: product }} logic={billingProductLogic} enableFormOnSubmit>
<div className="border-t border-border p-8" data-attr={`billing-limit-input-${product.type}`}>
<div className="border-t border-border p-8" data-attr={`billing-limit-input-wrapper-${product.type}`}>
<h4 className="mb-2">Billing limit</h4>
<div className="flex">
{!isEditingBillingLimit ? (
<div className="flex items-center justify-center gap-1">
{hasCustomLimit ? (
{hasCustomLimitSet ? (
<>
{usingInitialBillingLimit ? (
<Tooltip title="Initial limits protect you from accidentally incurring large unexpected charges. Some features may stop working and data may be dropped if your usage exceeds your limit.">
<span className="text-sm">
<span
className="text-sm"
data-attr={`default-billing-limit-${product.type}`}
>
This product has a default initial billing limit of{' '}
<b>${initialBillingLimit}</b>.
</span>
</Tooltip>
) : (
<Tooltip title="Set a billing limit to control your recurring costs. Some features may stop working and data may be dropped if your usage exceeds your limit.">
<span className="text-sm">
<span className="text-sm" data-attr={`billing-limit-set-${product.type}`}>
You have a <b>${customLimitUsd}</b> billing limit set for{' '}
{product?.name?.toLowerCase()}.
</span>
Expand All @@ -62,7 +64,7 @@ export const BillingLimit = ({ product }: { product: BillingProductV2Type }): JS
</>
) : (
<>
<span className="text-sm">
<span className="text-sm" data-attr={`billing-limit-not-set-${product.type}`}>
You do not have a billing limit set for {product?.name?.toLowerCase()}.
</span>
<LemonButton
Expand All @@ -87,6 +89,7 @@ export const BillingLimit = ({ product }: { product: BillingProductV2Type }): JS
fullWidth={false}
status={error ? 'danger' : 'default'}
value={value}
data-attr={`billing-limit-input-${product.type}`}
onChange={onChange}
prefix={<b>$</b>}
disabled={billingLoading}
Expand All @@ -100,7 +103,13 @@ export const BillingLimit = ({ product }: { product: BillingProductV2Type }): JS
)}
</Field>

<LemonButton loading={billingLoading} type="primary" size="small" htmlType="submit">
<LemonButton
loading={billingLoading}
type="primary"
size="small"
htmlType="submit"
data-attr={`save-billing-limit-${product.type}`}
>
Save
</LemonButton>
<LemonButton
Expand All @@ -113,13 +122,14 @@ export const BillingLimit = ({ product }: { product: BillingProductV2Type }): JS
>
Cancel
</LemonButton>
{hasCustomLimit ? (
{hasCustomLimitSet ? (
<LemonButton
status="danger"
size="small"
data-attr={`remove-billing-limit-${product.type}`}
tooltip="Remove billing limit"
onClick={() => {
setBillingLimitInput(undefined)
setBillingLimitInput(null)
submitBillingLimitInput()
}}
>
Expand Down
Loading

0 comments on commit bf50012

Please sign in to comment.