Skip to content

Commit

Permalink
Merge branch 'master' into debug_events
Browse files Browse the repository at this point in the history
* master:
  chore(data-warehouse): make sure exception is passed through at workflow step (#23409)
  feat: add a launch compound for posthog with local billing (#23410)
  chore: add backfill_personless_distinct_ids command (#23404)
  fix: add missing billing tests (#23408)
  Schema-Enforcer plugin not global (#23412)
  chore: Enable person batch exports only on supported destinations (#23354)
  fix: allow entering a custom value while property values load (#23405)
  perf: Materialize elements_chain (#23170)
  fix(experiments): provide `required_scope` for experiments API (#23385)
  feat(survey): Allow events to repeatedly activate surveys (#23238)
  chore: maybe this will stop them flapping (#23401)
  chore(data-warehouse): Added number formatting for source settings (#23221)
  fix(multi project flags): remove flag id from URL when switching projects (#23394)
  • Loading branch information
fuziontech committed Jul 2, 2024
2 parents a9a7c98 + 0c176a5 commit 2d1b3c3
Show file tree
Hide file tree
Showing 26 changed files with 526 additions and 78 deletions.
18 changes: 17 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"configurations": [
{
"name": "Frontend",
"command": "npm start",
"command": "pnpm start",
"request": "launch",
"type": "node-terminal",
"cwd": "${workspaceFolder}",
Expand Down Expand Up @@ -233,6 +233,22 @@
"order": 1,
"group": "compound"
}
},
{
"name": "PostHog (local billing)",
"configurations": [
"Backend (with local billing)",
"Celery Threaded Pool",
"Celery Redbeat Scheduler",
"Frontend",
"Plugin Server",
"Temporal Worker"
],
"stopAll": true,
"presentation": {
"order": 2,
"group": "compound"
}
}
]
}
90 changes: 90 additions & 0 deletions ee/api/test/test_billing.py
Original file line number Diff line number Diff line change
Expand Up @@ -837,3 +837,93 @@ def mock_implementation(url: str, headers: Any = None, params: Any = None) -> Ma
self.organization.refresh_from_db()

assert self.organization.customer_trust_scores == {"recordings": 0, "events": 15, "rows_synced": 0}


class TestActivateBillingAPI(APILicensedTest):
def test_activate_success(self):
url = "/api/billing/activate"
data = {"products": "product_1:plan_1,product_2:plan_2", "redirect_path": "custom/path"}

response = self.client.get(url, data=data)
self.assertEqual(response.status_code, status.HTTP_302_FOUND)

self.assertIn("/activate", response.url)
self.assertIn("products=product_1:plan_1,product_2:plan_2", response.url)
url_pattern = r"redirect_uri=http://[^/]+/custom/path"
self.assertRegex(response.url, url_pattern)

def test_deprecated_activation_success(self):
url = "/api/billing/activate"
data = {"products": "product_1:plan_1,product_2:plan_2", "redirect_path": "custom/path"}

response = self.client.get(url, data=data)
self.assertEqual(response.status_code, status.HTTP_302_FOUND)

self.assertIn("/activate", response.url)
self.assertIn("products=product_1:plan_1,product_2:plan_2", response.url)
url_pattern = r"redirect_uri=http://[^/]+/custom/path"
self.assertRegex(response.url, url_pattern)

def test_activate_with_default_redirect_path(self):
url = "/api/billing/activate"
data = {
"products": "product_1:plan_1,product_2:plan_2",
}

response = self.client.get(url, data)

self.assertEqual(response.status_code, status.HTTP_302_FOUND)
self.assertIn("products=product_1:plan_1,product_2:plan_2", response.url)
url_pattern = r"redirect_uri=http://[^/]+/organization/billing"
self.assertRegex(response.url, url_pattern)

def test_activate_failure(self):
url = "/api/billing/activate"
data = {"none": "nothing"}

response = self.client.get(url, data)

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_activate_with_plan_error(self):
url = "/api/billing/activate"
data = {"plan": "plan"}

response = self.client.get(url, data)

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
response.json(),
{
"attr": "plan",
"code": "invalid_input",
"detail": "The 'plan' parameter is no longer supported. Please use the 'products' parameter instead.",
"type": "validation_error",
},
)

@patch("ee.billing.billing_manager.BillingManager.deactivate_products")
@patch("ee.billing.billing_manager.BillingManager.get_billing")
def test_deactivate_success(self, mock_get_billing, mock_deactivate_products):
mock_deactivate_products.return_value = MagicMock()
mock_get_billing.return_value = {
"available_features": [],
"products": [],
}

url = "/api/billing/deactivate"
data = {"products": "product_1"}

response = self.client.get(url, data)

self.assertEqual(response.status_code, status.HTTP_200_OK)
mock_deactivate_products.assert_called_once_with(self.organization, "product_1")
mock_get_billing.assert_called_once_with(self.organization, None)

def test_deactivate_failure(self):
url = "/api/billing/deactivate"
data = {"none": "nothing"}

response = self.client.get(url, data)

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
8 changes: 4 additions & 4 deletions ee/clickhouse/views/experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ class ClickhouseExperimentsViewSet(TeamAndOrgViewSetMixin, viewsets.ModelViewSet
# 1. Probability of success
# 2. Funnel breakdown graph to display
# ******************************************
@action(methods=["GET"], detail=True)
@action(methods=["GET"], detail=True, required_scopes=["experiment:read"])
def results(self, request: Request, *args: Any, **kwargs: Any) -> Response:
experiment: Experiment = self.get_object()

Expand All @@ -314,7 +314,7 @@ def results(self, request: Request, *args: Any, **kwargs: Any) -> Response:
#
# Returns values for secondary experiment metrics, broken down by variants
# ******************************************
@action(methods=["GET"], detail=True)
@action(methods=["GET"], detail=True, required_scopes=["experiment:read"])
def secondary_results(self, request: Request, *args: Any, **kwargs: Any) -> Response:
experiment: Experiment = self.get_object()

Expand Down Expand Up @@ -347,15 +347,15 @@ def secondary_results(self, request: Request, *args: Any, **kwargs: Any) -> Resp
# 1. Probability of success
# 2. Funnel breakdown graph to display
# ******************************************
@action(methods=["GET"], detail=False)
@action(methods=["GET"], detail=False, required_scopes=["experiment:read"])
def requires_flag_implementation(self, request: Request, *args: Any, **kwargs: Any) -> Response:
filter = Filter(request=request, team=self.team).shallow_clone({"date_from": "-7d", "date_to": ""})

warning = requires_flag_warning(filter, self.team)

return Response({"result": warning})

@action(methods=["POST"], detail=True)
@action(methods=["POST"], detail=True, required_scopes=["experiment:write"])
def create_exposure_cohort_for_experiment(self, request: Request, *args: Any, **kwargs: Any) -> Response:
experiment = self.get_object()
flag = getattr(experiment, "feature_flag", None)
Expand Down
Binary file modified frontend/__snapshots__/exporter-exporter--dashboard--dark.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.
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export function PropertyValue({
loading={options[propertyKey]?.status === 'loading'}
value={formattedValues}
mode={isMultiSelect ? 'multiple' : 'single'}
allowCustomValues={options[propertyKey]?.allowCustomValues}
allowCustomValues={options[propertyKey] ? options[propertyKey].allowCustomValues : true}
onChange={(nextVal) => (isMultiSelect ? setValue(nextVal) : setValue(nextVal[0]))}
onInputChange={onSearchTextChange}
placeholder={placeholder}
Expand Down
25 changes: 15 additions & 10 deletions frontend/src/scenes/batch_exports/BatchExportEditForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,21 @@ export function BatchExportGeneralEditFields({
<LemonInput placeholder="Name your workflow for future reference" />
</LemonField>
)}
{featureFlags[FEATURE_FLAGS.PERSON_BATCH_EXPORTS] && (
<LemonField name="model" label="Model" info="A model defines the data that will be exported.">
<LemonSelect
options={[
{ value: 'events', label: 'Events' },
{ value: 'persons', label: 'Persons' },
]}
/>
</LemonField>
)}
{featureFlags[FEATURE_FLAGS.PERSON_BATCH_EXPORTS] &&
// Suppported destinations for Persons batch exports.
// TODO: Add them all once supported
(batchExportConfigForm.destination === 'S3' ||
batchExportConfigForm.destination === 'BigQuery' ||
batchExportConfigForm.destination === 'Snowflake') && (
<LemonField name="model" label="Model" info="A model defines the data that will be exported.">
<LemonSelect
options={[
{ value: 'events', label: 'Events' },
{ value: 'persons', label: 'Persons' },
]}
/>
</LemonField>
)}
<div className="flex gap-2 items-start flex-wrap">
<LemonField
name="interval"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ export function DataWarehouseManagedSourcesTable(): JSX.Element {
key: 'rows_synced',
tooltip: 'Total number of rows synced across all schemas in this source',
render: function RenderRowsSynced(_, source) {
return source.schemas.reduce((acc, schema) => acc + (schema.table?.row_count ?? 0), 0)
return source.schemas
.reduce((acc, schema) => acc + (schema.table?.row_count ?? 0), 0)
.toLocaleString()
},
},
{
Expand Down Expand Up @@ -392,7 +394,7 @@ const SchemaTable = ({ schemas }: SchemaTableProps): JSX.Element => {
title: 'Rows Synced',
key: 'rows_synced',
render: function Render(_, schema) {
return schema.table?.row_count ?? ''
return (schema.table?.row_count ?? 0).toLocaleString()
},
},
{
Expand Down
52 changes: 30 additions & 22 deletions frontend/src/scenes/pipeline/PipelineBatchExportConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,29 +112,37 @@ export function PipelineBatchExportConfiguration({ service, id }: { service?: st
<LemonInput type="text" />
</LemonField>

{featureFlags[FEATURE_FLAGS.PERSON_BATCH_EXPORTS] && (
<>
<LemonField
name="model"
label="Model"
info="A model defines the data that will be exported."
>
<LemonSelect
options={tables.map((table) => ({ value: table.name, label: table.id }))}
value={selectedModel}
onSelect={(newValue) => {
setSelectedModel(newValue)
}}
/>
</LemonField>
{featureFlags[FEATURE_FLAGS.PERSON_BATCH_EXPORTS] &&
// Suppported destinations for Persons batch exports.
// TODO: Add configuration all once supported
(configuration.destination === 'S3' ||
configuration.destination === 'BigQuery' ||
configuration.destination === 'Snowflake') && (
<>
<LemonField
name="model"
label="Model"
info="A model defines the data that will be exported."
>
<LemonSelect
options={tables.map((table) => ({
value: table.name,
label: table.id,
}))}
value={selectedModel}
onSelect={(newValue) => {
setSelectedModel(newValue)
}}
/>
</LemonField>

<DatabaseTable
table={selectedModel ? selectedModel : 'events'}
tables={tables}
inEditSchemaMode={false}
/>
</>
)}
<DatabaseTable
table={selectedModel ? selectedModel : 'events'}
tables={tables}
inEditSchemaMode={false}
/>
</>
)}
</div>
<div className="border bg-bg-light p-3 rounded flex-2 min-w-100">
<BatchExportConfigurationFields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ function getEventTable(service: BatchExportService['type']): DatabaseSchemaBatch
team_id: {
name: 'team_id',
hogql_value: service == 'Postgres' || service == 'Redshift' ? 'toInt32(team_id)' : 'team_id',
type: 'string',
type: 'integer',
schema_valid: true,
},
set: {
Expand Down Expand Up @@ -155,7 +155,7 @@ const personsTable: DatabaseSchemaBatchExportTable = {
team_id: {
name: 'team_id',
hogql_value: 'team_id',
type: 'string',
type: 'integer',
schema_valid: true,
},
distinct_id: {
Expand All @@ -176,6 +176,12 @@ const personsTable: DatabaseSchemaBatchExportTable = {
type: 'json',
schema_valid: true,
},
version: {
name: 'version',
hogql_value: 'version',
type: 'integer',
schema_valid: true,
},
},
}

Expand Down
1 change: 0 additions & 1 deletion frontend/src/scenes/pipeline/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ const PLUGINS_ALLOWED_WITHOUT_DATA_PIPELINES_ARR = [
// filtering apps
'https://github.com/PostHog/downsampling-plugin',
'https://github.com/PostHog/posthog-filter-out-plugin',
'https://github.com/PostHog/schema-enforcer-plugin',
// transformation apps
'https://github.com/PostHog/language-url-splitter-app',
'https://github.com/PostHog/posthog-app-url-parameters-to-event-properties',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ export const sessionRecordingPlayerLogic = kea<sessionRecordingPlayerLogicType>(
* at least paints the "played" portion of the recording correctly
**/
actions.setPause()
}, 100)
}, 400)
}
}
},
Expand Down
Loading

0 comments on commit 2d1b3c3

Please sign in to comment.