Secure connexion between TinyBird and webhookResponseGraph (#7913)

TLDR:
Secure connexion between tinybird and twenty using jwt when accessing
datasource from tinybird.

Solves:
https://github.com/twentyhq/private-issues/issues/73


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: Charles Bochet <charles@twenty.com>
This commit is contained in:
Ana Sofia Marin Alexandre
2024-10-21 12:42:52 -03:00
committed by GitHub
parent edf4ae084b
commit 373926b895
19 changed files with 178 additions and 46 deletions

View File

@ -1,6 +1,6 @@
import { useGraphData } from '@/settings/developers/webhook/hooks/useGraphData';
import { webhookGraphDataState } from '@/settings/developers/webhook/states/webhookGraphDataState';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useSetRecoilState } from 'recoil';
type SettingsDevelopersWebhookUsageGraphEffectProps = {
@ -11,14 +11,18 @@ export const SettingsDevelopersWebhookUsageGraphEffect = ({
webhookId,
}: SettingsDevelopersWebhookUsageGraphEffectProps) => {
const setWebhookGraphData = useSetRecoilState(webhookGraphDataState);
const [isLoaded, setIsLoaded] = useState(false);
const { fetchGraphData } = useGraphData(webhookId);
useEffect(() => {
fetchGraphData('7D').then((graphInput) => {
setWebhookGraphData(graphInput);
});
}, [fetchGraphData, setWebhookGraphData, webhookId]);
if (!isLoaded) {
fetchGraphData('7D').then((graphInput) => {
setWebhookGraphData(graphInput);
});
setIsLoaded(true);
}
}, [fetchGraphData, isLoaded, setWebhookGraphData, webhookId]);
return <></>;
};

View File

@ -0,0 +1,47 @@
import { renderHook } from '@testing-library/react';
import { CurrentUser, currentUserState } from '@/auth/states/currentUserState';
import { useAnalyticsTinybirdJwt } from '@/settings/developers/webhook/hooks/useAnalyticsTinybirdJwt';
import { act } from 'react';
import { useSetRecoilState } from 'recoil';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
const Wrapper = getJestMetadataAndApolloMocksWrapper({
apolloMocks: [],
});
describe('useAnalyticsTinybirdJwt', () => {
it('should return the analytics jwt token', async () => {
const { result } = renderHook(
() => {
const setCurrentUserState = useSetRecoilState(currentUserState);
return {
useAnalyticsTinybirdJwt: useAnalyticsTinybirdJwt(),
setCurrentUserState,
};
},
{ wrapper: Wrapper },
);
act(() => {
result.current.setCurrentUserState({
analyticsTinybirdJwt: 'jwt',
} as CurrentUser);
});
expect(result.current.useAnalyticsTinybirdJwt).toBe('jwt');
act(() => {
result.current.setCurrentUserState(null);
});
expect(result.current.useAnalyticsTinybirdJwt).toBeUndefined();
act(() => {
result.current.setCurrentUserState({} as CurrentUser);
});
expect(result.current.useAnalyticsTinybirdJwt).toBeUndefined();
});
});

View File

@ -0,0 +1,18 @@
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { isNull } from '@sniptt/guards';
export const useAnalyticsTinybirdJwt = (): string | undefined => {
const currentUser = useRecoilValue(currentUserState);
if (!currentUser) {
return undefined;
}
if (isNull(currentUser.analyticsTinybirdJwt)) {
return undefined;
}
return currentUser.analyticsTinybirdJwt;
};

View File

@ -1,16 +1,24 @@
import { useAnalyticsTinybirdJwt } from '@/settings/developers/webhook/hooks/useAnalyticsTinybirdJwt';
import { fetchGraphDataOrThrow } from '@/settings/developers/webhook/utils/fetchGraphDataOrThrow';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { isUndefined } from '@sniptt/guards';
export const useGraphData = (webhookId: string) => {
const { enqueueSnackBar } = useSnackBar();
const analyticsTinybirdJwt = useAnalyticsTinybirdJwt();
const fetchGraphData = async (
windowLengthGraphOption: '7D' | '1D' | '12H' | '4H',
) => {
try {
if (isUndefined(analyticsTinybirdJwt)) {
throw new Error('No analyticsTinybirdJwt found');
}
return await fetchGraphDataOrThrow({
webhookId,
windowLength: windowLengthGraphOption,
tinybirdJwt: analyticsTinybirdJwt,
});
} catch (error) {
enqueueSnackBar('Something went wrong while fetching webhook usage', {

View File

@ -4,22 +4,24 @@ import { WEBHOOK_GRAPH_API_OPTIONS_MAP } from '@/settings/developers/webhook/con
type fetchGraphDataOrThrowProps = {
webhookId: string;
windowLength: '7D' | '1D' | '12H' | '4H';
tinybirdJwt: string;
};
export const fetchGraphDataOrThrow = async ({
webhookId,
windowLength,
tinybirdJwt,
}: fetchGraphDataOrThrowProps) => {
const queryString = new URLSearchParams({
...WEBHOOK_GRAPH_API_OPTIONS_MAP[windowLength],
webhookIdRequest: webhookId,
}).toString();
const token = 'REPLACE_ME';
const response = await fetch(
`https://api.eu-central-1.aws.tinybird.co/v0/pipes/getWebhooksAnalyticsV2.json?${queryString}`,
{
headers: {
Authorization: 'Bearer ' + token,
Authorization: 'Bearer ' + tinybirdJwt,
},
},
);