Assert filters are tuples, simplify schema (#4541)

This commit is contained in:
Artur Pata 2024-09-10 18:01:42 +03:00 committed by GitHub
parent e8d544c841
commit 52b94842c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 142 additions and 134 deletions

View File

@ -17,11 +17,11 @@ defmodule Plausible.Stats.JSONSchema do
@internal_query_schema @raw_public_schema @internal_query_schema @raw_public_schema
# Add overrides for things allowed in the internal API # Add overrides for things allowed in the internal API
|> JSONPointer.add!( |> JSONPointer.add!(
"#/definitions/filter_entry/oneOf/0/items/0/enum/0", "#/definitions/filter_operation_without_goals/enum/0",
"matches_wildcard" "matches_wildcard"
) )
|> JSONPointer.add!( |> JSONPointer.add!(
"#/definitions/filter_entry/oneOf/0/items/0/enum/0", "#/definitions/filter_operation_without_goals/enum/0",
"matches_wildcard_not" "matches_wildcard_not"
) )
|> JSONPointer.add!("#/definitions/metric/oneOf/0", %{ |> JSONPointer.add!("#/definitions/metric/oneOf/0", %{

View File

@ -29,9 +29,7 @@
}, },
"filters": { "filters": {
"type": "array", "type": "array",
"items": { "items": { "$ref": "#/definitions/filter_tree" },
"$ref": "#/definitions/filter_entry"
},
"description": "How to drill into your data" "description": "How to drill into your data"
}, },
"order_by": { "order_by": {
@ -54,11 +52,7 @@
} }
} }
}, },
"required": [ "required": ["site_id", "metrics", "date_range"],
"site_id",
"metrics",
"date_range"
],
"additionalProperties": false, "additionalProperties": false,
"definitions": { "definitions": {
"date_range": { "date_range": {
@ -97,23 +91,21 @@
}, },
{ {
"type": "array", "type": "array",
"additionalItems": false,
"minItems": 2,
"maxItems": 2,
"items": { "items": {
"type": "string", "type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}(?:T\\d{2}:\\d{2}:\\d{2}\\s[A-Za-z/_]+)?$" "pattern": "^\\d{4}-\\d{2}-\\d{2}(?:T\\d{2}:\\d{2}:\\d{2}\\s[A-Za-z/_]+)?$"
}, },
"markdownDescription": "A list of two elements to determine the query date range. Both elements should have the same format - either `YYYY-MM-DD` or `YYYY-MM-DDThh:mm:ss <timezone>`", "markdownDescription": "A list of two elements to determine the query date range. Both elements should have the same format - either `YYYY-MM-DD` or `YYYY-MM-DDThh:mm:ss <timezone>`",
"examples": [ "examples": [
[ ["2024-01-01", "2024-01-31"],
"2024-01-01",
"2024-01-31"
],
[ [
"2024-01-01T00:00:00 Europe/Tallinn", "2024-01-01T00:00:00 Europe/Tallinn",
"2024-01-01T12:00:00 Europe/Tallinn" "2024-01-01T12:00:00 Europe/Tallinn"
] ]
], ]
"minItems": 2,
"maxItems": 2
} }
] ]
}, },
@ -196,10 +188,7 @@
"type": "string", "type": "string",
"pattern": "^event:props:.+", "pattern": "^event:props:.+",
"markdownDescription": "Custom property. See [documentation](https://plausible.io/docs/custom-props/introduction) for more information", "markdownDescription": "Custom property. See [documentation](https://plausible.io/docs/custom-props/introduction) for more information",
"examples": [ "examples": ["event:props:url", "event:props:path"]
"event:props:url",
"event:props:path"
]
}, },
"goal_dimension": { "goal_dimension": {
"const": "event:goal", "const": "event:goal",
@ -207,28 +196,14 @@
}, },
"time_dimensions": { "time_dimensions": {
"type": "string", "type": "string",
"enum": [ "enum": ["time", "time:month", "time:week", "time:day", "time:hour"]
"time",
"time:month",
"time:week",
"time:day",
"time:hour"
]
}, },
"dimensions": { "dimensions": {
"oneOf": [ "oneOf": [
{ { "$ref": "#/definitions/simple_filter_dimensions" },
"$ref": "#/definitions/simple_filter_dimensions" { "$ref": "#/definitions/custom_property_filter_dimensions" },
}, { "$ref": "#/definitions/goal_dimension" },
{ { "$ref": "#/definitions/time_dimensions" }
"$ref": "#/definitions/custom_property_filter_dimensions"
},
{
"$ref": "#/definitions/goal_dimension"
},
{
"$ref": "#/definitions/time_dimensions"
}
] ]
}, },
"clauses": { "clauses": {
@ -237,129 +212,108 @@
"type": ["string", "integer"] "type": ["string", "integer"]
} }
}, },
"filter_operation_without_goals": {
"type": "string",
"enum": ["is_not", "contains_not", "matches", "matches_not"],
"description": "filter operation"
},
"filter_operation_with_goals": {
"type": "string",
"enum": ["is", "contains"],
"description": "filter operation"
},
"filter_without_goals": {
"type": "array",
"additionalItems": false,
"minItems": 3,
"maxItems": 3,
"items": [
{ "$ref": "#/definitions/filter_operation_without_goals" },
{
"oneOf": [
{ "$ref": "#/definitions/simple_filter_dimensions" },
{ "$ref": "#/definitions/custom_property_filter_dimensions" }
]
},
{ "$ref": "#/definitions/clauses" }
]
},
"filter_with_goals": {
"type": "array",
"additionalItems": false,
"minItems": 3,
"maxItems": 3,
"items": [
{
"$ref": "#/definitions/filter_operation_with_goals"
},
{
"oneOf": [
{ "$ref": "#/definitions/goal_dimension" },
{ "$ref": "#/definitions/simple_filter_dimensions" },
{ "$ref": "#/definitions/custom_property_filter_dimensions" }
]
},
{
"$ref": "#/definitions/clauses"
}
]
},
"filter_entry": { "filter_entry": {
"oneOf": [ "oneOf": [
{ { "$ref": "#/definitions/filter_without_goals" },
"type": "array", { "$ref": "#/definitions/filter_with_goals" }
"items": [ ]
{
"type": "string",
"enum": [
"is_not",
"contains_not",
"matches",
"matches_not"
],
"description": "filter operation"
}, },
{ "filter_tree": {
"oneOf": [ "oneOf": [
{ { "$ref": "#/definitions/filter_entry" },
"$ref": "#/definitions/simple_filter_dimensions" { "$ref": "#/definitions/filter_and_or" },
}, { "$ref": "#/definitions/filter_not" }
{
"$ref": "#/definitions/custom_property_filter_dimensions"
}
]
},
{
"$ref": "#/definitions/clauses"
}
]
},
{
"type": "array",
"items": [
{
"type": "string",
"enum": [
"is",
"contains"
],
"description": "filter operation"
},
{
"oneOf": [
{
"$ref": "#/definitions/goal_dimension"
},
{
"$ref": "#/definitions/simple_filter_dimensions"
},
{
"$ref": "#/definitions/custom_property_filter_dimensions"
}
]
},
{
"$ref": "#/definitions/clauses"
}
]
},
{
"$ref": "#/definitions/filter_and_or"
},
{
"$ref": "#/definitions/filter_not"
}
] ]
}, },
"filter_not": { "filter_not": {
"type": "array", "type": "array",
"items": [ "additionalItems": false,
{ "minItems": 2,
"const": "not" "maxItems": 2,
}, "items": [{ "const": "not" }, { "$ref": "#/definitions/filter_tree" }]
{
"$ref": "#/definitions/filter_entry"
}
]
}, },
"filter_and_or": { "filter_and_or": {
"type": "array", "type": "array",
"additionalItems": false,
"minItems": 2,
"maxItems": 2,
"items": [ "items": [
{ {
"type": "string", "type": "string",
"enum": [ "enum": ["and", "or"]
"and",
"or"
]
}, },
{ {
"type": "array", "type": "array",
"items": { "items": { "$ref": "#/definitions/filter_tree" },
"$ref": "#/definitions/filter_entry"
},
"minItems": 1 "minItems": 1
} }
] ]
}, },
"order_by_entry": { "order_by_entry": {
"type": "array", "type": "array",
"additionalItems": false,
"minItems": 2,
"maxItems": 2,
"items": [ "items": [
{ {
"oneOf": [ "oneOf": [
{ { "$ref": "#/definitions/metric" },
"$ref": "#/definitions/metric" { "$ref": "#/definitions/simple_filter_dimensions" },
}, { "$ref": "#/definitions/custom_property_filter_dimensions" },
{ { "$ref": "#/definitions/time_dimensions" }
"$ref": "#/definitions/simple_filter_dimensions"
},
{
"$ref": "#/definitions/custom_property_filter_dimensions"
},
{
"$ref": "#/definitions/time_dimensions"
}
], ],
"markdownDescription": "Metric or dimension to order by. Must be listed under `metrics` or `dimensions`" "markdownDescription": "Metric or dimension to order by. Must be listed under `metrics` or `dimensions`"
}, },
{ {
"type": "string", "type": "string",
"enum": [ "enum": ["asc", "desc"],
"asc",
"desc"
],
"description": "Sorting order" "description": "Sorting order"
} }
] ]

View File

@ -238,6 +238,60 @@ defmodule Plausible.Stats.Filters.QueryParserTest do
end end
end end
for too_short_filter <- [
[],
["and"],
["or"],
["and", []],
["or", []],
["not"],
["is_not"],
["is_not", "event:name"]
] do
test "errors on too short filter #{inspect(too_short_filter)}", %{
site: site
} do
%{
"site_id" => site.domain,
"metrics" => ["visitors"],
"date_range" => "all",
"filters" => [
unquote(too_short_filter)
]
}
|> check_error(
site,
~s(#/filters/0: Invalid filter #{inspect(unquote(too_short_filter))})
)
end
end
valid_filter = ["is", "event:props:foobar", ["value"]]
for too_long_filter <- [
["and", [valid_filter], "extra"],
["or", [valid_filter], []],
["not", valid_filter, 1],
Enum.concat(valid_filter, [true])
] do
test "errors on too long filter #{inspect(too_long_filter)}", %{
site: site
} do
%{
"site_id" => site.domain,
"metrics" => ["visitors"],
"date_range" => "all",
"filters" => [
unquote(too_long_filter)
]
}
|> check_error(
site,
~s(#/filters/0: Invalid filter #{inspect(unquote(too_long_filter))})
)
end
end
test "filtering by invalid operation", %{site: site} do test "filtering by invalid operation", %{site: site} do
%{ %{
"site_id" => site.domain, "site_id" => site.domain,