refactor webhookAnalytics call and enrich analytics module (#8253)
**TLDR** Refactor WebhoonAnalytics Graph to a more abstract version AnalyticsGraph (in analytics module). Thus enabling the components to be used on different instances (ex: new endpoint, new kind of graph). **In order to test:** 1. Set ANALYTICS_ENABLED to true 2. Set TINYBIRD_JWT_TOKEN to the ADMIN token from the workspace twenty_analytics_playground 3. Set TINYBIRD_JWT_TOKEN to the datasource or your admin token from the workspace twenty_analytics_playground 4. Create a Webhook in twenty and set wich events it needs to track 5. Run twenty-worker in order to make the webhooks work. 6. Do your tasks in order to populate the data 7. Enter to settings> webhook>your webhook and the statistics section should be displayed. --------- Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
committed by
GitHub
parent
f9c076df31
commit
f06cdbdfc6
@ -23,6 +23,12 @@ Copy the "admin your@email" token from from https://app.tinybird.co/tokens and p
|
||||
** Auth successful!
|
||||
** Configuration written to .tinyb file, consider adding it to .gitignore
|
||||
```
|
||||
You can also log in using your twenty_analytics_token without passing into the interactive mode:
|
||||
```sh
|
||||
tb auth --token <your twenty_analytics_token >
|
||||
** Auth successful!
|
||||
** Configuration written to .tinyb file, consider adding it to .gitignore
|
||||
```
|
||||
To sync your changes to Tinybird use:
|
||||
```sh
|
||||
tb push --force --push-deps
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
SCHEMA >
|
||||
`action` String `json:$.action`,
|
||||
`action` LowCardinality(String) `json:$.action`,
|
||||
`timestamp` DateTime64(3) `json:$.timestamp`,
|
||||
`version` String `json:$.version`,
|
||||
`version` LowCardinality(String) `json:$.version`,
|
||||
`userId` String `json:$.userId` DEFAULT '',
|
||||
`workspaceId` String `json:$.workspaceId` DEFAULT '',
|
||||
`payload` String `json:$.payload`
|
||||
|
||||
ENGINE MergeTree
|
||||
ENGINE_PARTITION_KEY toYear(timestamp)
|
||||
ENGINE_SORTING_KEY action, timestamp
|
||||
ENGINE_SORTING_KEY action, workspaceId, timestamp
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
SCHEMA >
|
||||
`href` String `json:$.href`,
|
||||
`locale` String `json:$.locale`,
|
||||
`locale` LowCardinality(String) `json:$.locale`,
|
||||
`pathname` String `json:$.pathname`,
|
||||
`referrer` String `json:$.referrer`,
|
||||
`sessionId` String `json:$.sessionId`,
|
||||
`timeZone` String `json:$.timeZone`,
|
||||
`timeZone` LowCardinality(String) `json:$.timeZone`,
|
||||
`timestamp` DateTime64(3) `json:$.timestamp`,
|
||||
`userAgent` String `json:$.userAgent`,
|
||||
`userId` String `json:$.userId` DEFAULT '',
|
||||
`version` String `json:$.version`,
|
||||
`workspaceId` String `json:$.workspaceId`
|
||||
`version` LowCardinality(String) `json:$.version`,
|
||||
`workspaceId` String `json:$.workspaceId` DEFAULT ''
|
||||
|
||||
ENGINE MergeTree
|
||||
ENGINE_PARTITION_KEY toYear(timestamp)
|
||||
ENGINE_SORTING_KEY timestamp, userId, version, workspaceId
|
||||
ENGINE_SORTING_KEY workspaceId, userId, timestamp
|
||||
|
||||
@ -4,8 +4,9 @@ SCHEMA >
|
||||
`functionId` String,
|
||||
`durationInMs` Int64,
|
||||
`success` Bool,
|
||||
`errorType` String
|
||||
`errorType` LowCardinality(String),
|
||||
`version` LowCardinality(String)
|
||||
|
||||
ENGINE MergeTree
|
||||
ENGINE_PARTITION_KEY toYYYYMM(timestamp)
|
||||
ENGINE_SORTING_KEY timestamp, functionId, success
|
||||
ENGINE_SORTING_KEY workspaceId, functionId, timestamp
|
||||
|
||||
@ -3,11 +3,11 @@ SCHEMA >
|
||||
`workspaceId` String,
|
||||
`webhookId` String,
|
||||
`url` String,
|
||||
`success` UInt8,
|
||||
`success` Bool,
|
||||
`status` Int64,
|
||||
`eventName` String,
|
||||
`version` String
|
||||
`eventName` LowCardinality(String),
|
||||
`version` LowCardinality(String)
|
||||
|
||||
ENGINE MergeTree
|
||||
ENGINE_PARTITION_KEY toYYYYMM(timestamp)
|
||||
ENGINE_SORTING_KEY timestamp, workspaceId
|
||||
ENGINE_SORTING_KEY workspaceId, webhookId, timestamp
|
||||
|
||||
@ -6,18 +6,14 @@ SQL >
|
||||
%
|
||||
WITH
|
||||
toStartOfDay(
|
||||
toDateTime64({{ DateTime64(start, '2024-10-16 00:00:00.000') }}, 3),
|
||||
{{ String(timezone, 'UTC') }}
|
||||
parseDateTime64BestEffort({{ String(start, '2024-11-01T00:00:00.000Z') }}, 3)
|
||||
) AS start,
|
||||
toStartOfDay(
|
||||
toDateTime64({{ DateTime64(end, '2024-10-23 00:00:00.000') }}, 3),
|
||||
{{ String(timezone, 'UTC') }}
|
||||
) AS
|
||||
toStartOfDay(parseDateTime64BestEffort({{ String(end, '2024-11-02T00:00:00.000Z') }}, 3)) AS
|
||||
end
|
||||
SELECT
|
||||
arrayJoin(
|
||||
arrayMap(
|
||||
x -> toDateTime64(toStartOfDay(toDateTime64(x, 3), {{ String(timezone, 'UTC') }}), 3),
|
||||
x -> toDateTime64(toStartOfDay(toDateTime64(x, 3)), 3),
|
||||
range(toUInt32(start + 86400), toUInt32(end + 86400),
|
||||
86400
|
||||
)
|
||||
@ -29,13 +25,9 @@ SQL >
|
||||
%
|
||||
WITH
|
||||
toStartOfHour(
|
||||
toDateTime64({{ DateTime64(start, '2024-10-22 00:00:00.000') }}, 3),
|
||||
{{ String(timezone, 'UTC') }}
|
||||
parseDateTime64BestEffort({{ String(start, '2024-11-01T00:00:00.000Z') }}, 3)
|
||||
) AS start,
|
||||
toStartOfHour(
|
||||
toDateTime64({{ DateTime64(end, '2024-10-23 00:00:00.000') }}, 3),
|
||||
{{ String(timezone, 'UTC') }}
|
||||
) AS
|
||||
toStartOfHour(parseDateTime64BestEffort({{ String(end, '2024-11-02T00:00:00.000Z') }}, 3)) AS
|
||||
end
|
||||
SELECT
|
||||
arrayJoin(
|
||||
@ -43,45 +35,7 @@ SQL >
|
||||
)
|
||||
) as interval
|
||||
|
||||
NODE customIntervals
|
||||
SQL >
|
||||
%
|
||||
WITH
|
||||
time_series AS (
|
||||
SELECT
|
||||
toDateTime64(
|
||||
toDateTime(
|
||||
toStartOfInterval(
|
||||
toDateTime64({{ DateTime64(start, '2024-10-22 00:00:00.000') }}, 3),
|
||||
INTERVAL {{ Int32(tickIntervalInMinutes, 420) }} MINUTE
|
||||
)
|
||||
)
|
||||
+ INTERVAL number * {{ Int32(tickIntervalInMinutes, 420) }} MINUTE,
|
||||
3
|
||||
) AS interval
|
||||
FROM
|
||||
numbers(
|
||||
0,
|
||||
1 + intDiv(
|
||||
dateDiff(
|
||||
'minute',
|
||||
toDateTime64({{ DateTime64(start, '2024-10-22 00:00:00.000') }}, 3),
|
||||
toDateTime64({{ DateTime64(end, '2024-10-23 00:00:00.000') }}, 3)
|
||||
),
|
||||
{{ Int32(tickIntervalInMinutes, 420) }}
|
||||
)
|
||||
)
|
||||
WHERE interval <= toDateTime64({{ DateTime64(end, '2024-10-23 00:00:00.000') }}, 3)
|
||||
)
|
||||
SELECT interval
|
||||
FROM time_series
|
||||
|
||||
NODE selectIntervalByGranularity
|
||||
SQL >
|
||||
%
|
||||
SELECT *
|
||||
FROM
|
||||
{% if granularity == "custom" %} customIntervals
|
||||
{% elif granularity == "hour" %} hourIntervals
|
||||
{% else %} dayIntervals
|
||||
{% end %}
|
||||
SELECT * FROM {% if granularity == "hour" %} hourIntervals {% else %} dayIntervals {% end %}
|
||||
|
||||
@ -4,14 +4,8 @@ NODE timeSeriesServerlessFunctionDurationData
|
||||
SQL >
|
||||
%
|
||||
SELECT
|
||||
{% if granularity == "hour" %} toStartOfHour(timestamp, {{ String(timezone, 'UTC') }})
|
||||
{% elif granularity == "custom" %}
|
||||
toDateTime64(
|
||||
toStartOfMinute(timestamp, {{ String(timezone, 'UTC') }}),
|
||||
3,
|
||||
{{ String(timezone, 'UTC') }}
|
||||
)
|
||||
{% else %} toDateTime64(toStartOfDay(timestamp, {{ String(timezone, 'UTC') }}), 3)
|
||||
{% if granularity == "hour" %} toStartOfHour(timestamp)
|
||||
{% else %} toDateTime64(toStartOfDay(timestamp), 3)
|
||||
{% end %} AS interval,
|
||||
avg(CAST(durationInMs AS Float64)) as average,
|
||||
min(durationInMs) as minimum,
|
||||
@ -19,11 +13,11 @@ SQL >
|
||||
FROM serverlessFunctionEventMV
|
||||
WHERE
|
||||
true
|
||||
AND functionId = {{ String(functionId, 'a9fd87c0-af86-4e17-be3a-a6d3d961678a', required=True) }}
|
||||
AND workspaceId
|
||||
={{ String(workspaceId, '20202020-1c25-4d02-bf25-6aeccf7ea419', required=True) }}
|
||||
AND timestamp >= {{ DateTime(start, '2024-10-22 00:00:00') }}
|
||||
AND timestamp < {{ DateTime(end, '2024-10-23 00:00:00') }}
|
||||
AND functionId = {{ String(functionId, 'a9fd87c0-af86-4e17-be3a-a6d3d961678a', required=True) }}
|
||||
AND timestamp >= parseDateTime64BestEffort({{ String(start, '2024-11-01T00:00:00.000Z') }}, 3)
|
||||
AND timestamp < parseDateTime64BestEffort({{ String(end, '2024-11-02T00:00:00.000Z') }}, 3)
|
||||
GROUP BY interval
|
||||
ORDER BY interval
|
||||
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
INCLUDE "../includes/timeSeries.incl"
|
||||
-- I decided to separate the error count and the success rate because I think we should maintain the bijection
|
||||
-- between an endpoint and a graph in the front-end.
|
||||
NODE timeSeriesServerlessFunctionErrorCountData
|
||||
SQL >
|
||||
%
|
||||
SELECT
|
||||
{% if granularity == "hour" %} toStartOfHour(timestamp)
|
||||
{% else %} toDateTime64(toStartOfDay(timestamp), 3)
|
||||
{% end %} AS interval,
|
||||
uniqIf(*, success = false) as error_count
|
||||
FROM serverlessFunctionEventMV
|
||||
WHERE
|
||||
true
|
||||
AND workspaceId
|
||||
={{ String(workspaceId, '20202020-1c25-4d02-bf25-6aeccf7ea419', required=True) }}
|
||||
AND functionId = {{ String(functionId, 'ad018fc5-eace-4f7e-942f-929560a16459', required=True) }}
|
||||
AND timestamp >= parseDateTime64BestEffort({{ String(start, '2024-11-01T00:00:00.000Z') }}, 3)
|
||||
AND timestamp < parseDateTime64BestEffort({{ String(end, '2024-11-02T00:00:00.000Z') }}, 3)
|
||||
GROUP BY interval
|
||||
ORDER BY interval
|
||||
|
||||
NODE endpoint
|
||||
SQL >
|
||||
%
|
||||
SELECT formatDateTime(interval, '%FT%T.000%z') as start, error_count
|
||||
FROM selectIntervalByGranularity
|
||||
LEFT JOIN timeSeriesServerlessFunctionErrorCountData USING interval
|
||||
@ -1,41 +0,0 @@
|
||||
INCLUDE "../includes/timeSeries.incl"
|
||||
|
||||
NODE timeSeriesServerlessFunctionErrorsData
|
||||
SQL >
|
||||
%
|
||||
SELECT
|
||||
{% if granularity == "hour" %} toStartOfHour(timestamp, {{ String(timezone, 'UTC') }})
|
||||
{% elif granularity == "custom" %}
|
||||
toDateTime64(
|
||||
toStartOfMinute(timestamp, {{ String(timezone, 'UTC') }}),
|
||||
3,
|
||||
{{ String(timezone, 'UTC') }}
|
||||
)
|
||||
{% else %} toDateTime64(toStartOfDay(timestamp, {{ String(timezone, 'UTC') }}), 3)
|
||||
{% end %} AS interval,
|
||||
uniqIf(*, success = false) as error_count,
|
||||
round(
|
||||
if(
|
||||
uniqIf(*, success = true) = 0,
|
||||
0,
|
||||
(uniqIf(*, success = true) - uniqIf(*, success = false)) / uniqIf(*, success = true)
|
||||
),
|
||||
2
|
||||
) as success_rate
|
||||
FROM serverlessFunctionEventMV
|
||||
WHERE
|
||||
true
|
||||
AND functionId = {{ String(functionId, 'a9fd87c0-af86-4e17-be3a-a6d3d961678a', required=True) }}
|
||||
AND workspaceId
|
||||
={{ String(workspaceId, '20202020-1c25-4d02-bf25-6aeccf7ea419', required=True) }}
|
||||
AND timestamp >= {{ DateTime(start, '2024-10-22 00:00:00') }}
|
||||
AND timestamp < {{ DateTime(end, '2024-10-23 00:00:00') }}
|
||||
GROUP BY interval
|
||||
ORDER BY interval
|
||||
|
||||
NODE endpoint
|
||||
SQL >
|
||||
%
|
||||
SELECT formatDateTime(interval, '%FT%T.000%z') as start, error_count, success_rate
|
||||
FROM selectIntervalByGranularity
|
||||
LEFT JOIN timeSeriesServerlessFunctionErrorsData USING interval
|
||||
@ -0,0 +1,34 @@
|
||||
INCLUDE "../includes/timeSeries.incl"
|
||||
|
||||
NODE timeSeriesServerlessFunctionSuccessRateData
|
||||
SQL >
|
||||
%
|
||||
SELECT
|
||||
{% if granularity == "hour" %} toStartOfHour(timestamp)
|
||||
{% else %} toDateTime64(toStartOfDay(timestamp), 3)
|
||||
{% end %} AS interval,
|
||||
round(
|
||||
if(
|
||||
uniqIf(*, success = true) = 0,
|
||||
0,
|
||||
(uniqIf(*, success = true) - uniqIf(*, success = false)) / uniqIf(*, success = true)
|
||||
),
|
||||
2
|
||||
) as success_rate
|
||||
FROM serverlessFunctionEventMV
|
||||
WHERE
|
||||
true
|
||||
AND workspaceId
|
||||
={{ String(workspaceId, '20202020-1c25-4d02-bf25-6aeccf7ea419', required=True) }}
|
||||
AND functionId = {{ String(functionId, 'ad018fc5-eace-4f7e-942f-929560a16459', required=True) }}
|
||||
AND timestamp >= parseDateTime64BestEffort({{ String(start, '2024-11-01T00:00:00.000Z') }}, 3)
|
||||
AND timestamp < parseDateTime64BestEffort({{ String(end, '2024-11-02T00:00:00.000Z') }}, 3)
|
||||
GROUP BY interval
|
||||
ORDER BY interval
|
||||
|
||||
NODE endpoint
|
||||
SQL >
|
||||
%
|
||||
SELECT formatDateTime(interval, '%FT%T.000%z') as start, success_rate
|
||||
FROM selectIntervalByGranularity
|
||||
LEFT JOIN timeSeriesServerlessFunctionSuccessRateData USING interval
|
||||
@ -4,25 +4,19 @@ NODE timeSeriesWebhookData
|
||||
SQL >
|
||||
%
|
||||
SELECT
|
||||
{% if granularity == "hour" %} toStartOfHour(timestamp, {{ String(timezone, 'UTC') }})
|
||||
{% elif granularity == "custom" %}
|
||||
toDateTime64(
|
||||
toStartOfMinute(timestamp, {{ String(timezone, 'UTC') }}),
|
||||
3,
|
||||
{{ String(timezone, 'UTC') }}
|
||||
)
|
||||
{% else %} toDateTime64(toStartOfDay(timestamp, {{ String(timezone, 'UTC') }}), 3)
|
||||
{% if granularity == "hour" %} toStartOfHour(timestamp)
|
||||
{% else %} toDateTime64(toStartOfDay(timestamp), 3)
|
||||
{% end %} AS interval,
|
||||
uniqIf(*, success = true) as success_count,
|
||||
uniqIf(*, success = false) as failure_count
|
||||
FROM webhookEventMV
|
||||
WHERE
|
||||
true
|
||||
AND webhookId = {{ String(webhookId, '90f12aed-0276-4bea-bcaa-c21ea2763d7d', required=True) }}
|
||||
AND workspaceId
|
||||
={{ String(workspaceId, '20202020-1c25-4d02-bf25-6aeccf7ea419', required=True) }}
|
||||
AND timestamp >= {{ DateTime(start, '2024-10-22 00:00:00') }}
|
||||
AND timestamp < {{ DateTime(end, '2024-10-23 00:00:00') }}
|
||||
AND webhookId = {{ String(webhookId, '5237a3bc-566d-4290-b951-96e91051f968', required=True) }}
|
||||
AND timestamp >= parseDateTime64BestEffort({{ String(start, '2024-11-01T00:00:00.000Z') }}, 3)
|
||||
AND timestamp < parseDateTime64BestEffort({{ String(end, '2024-11-02T00:00:00.000Z') }}, 3)
|
||||
GROUP BY interval
|
||||
ORDER BY interval
|
||||
|
||||
|
||||
@ -6,7 +6,8 @@ SQL >
|
||||
JSONExtractString(payload, 'functionId') as functionId,
|
||||
JSONExtractInt(payload, 'duration') as durationInMs,
|
||||
if(JSONExtractString(payload, 'status') = 'SUCCESS', TRUE, FALSE) as success,
|
||||
JSONExtractString(payload, 'errorType') as errorType
|
||||
JSONExtractString(payload, 'errorType') as errorType,
|
||||
version
|
||||
FROM event
|
||||
WHERE action = 'serverlessFunction.executed'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user