Skip to content

Commit

Permalink
Add webhook response graph from the last 5 days (#7487)
Browse files Browse the repository at this point in the history
#7346 #7343 #7342 #7344 

Before:

<img width="799" alt="Screenshot 2024-10-08 at 11 59 37"
src="https://github.com/user-attachments/assets/a1cd1714-41ed-4f96-85eb-2861e7a8b2c2">


Now:

![Screenshot 2024-10-07 at 18 56
21](https://github.com/user-attachments/assets/c87ee17a-c6c4-4938-b024-aaa635bab022)


In order to test:

1. Set ANALYTICS_ENABLED to true
2. Set TINYBIRD_TOKEN to your token from the workspace
_twenty_analytics_playground_
3. Write your client tinybird token in
SettingsDeveloppersWebhookDetail.tsx in line 93
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.
  • Loading branch information
anamarn authored Oct 9, 2024
1 parent 7987221 commit f901512
Show file tree
Hide file tree
Showing 16 changed files with 453 additions and 185 deletions.
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@
"@nestjs/serve-static": "^4.0.1",
"@nestjs/terminus": "^9.2.2",
"@nestjs/typeorm": "^10.0.0",
"@nivo/calendar": "^0.84.0",
"@nivo/core": "^0.84.0",
"@nx/eslint-plugin": "^17.2.8",
"@octokit/graphql": "^7.0.2",
"@ptc-org/nestjs-query-core": "^4.2.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/twenty-front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"workerDirectory": "public"
},
"dependencies": {
"@nivo/calendar": "^0.87.0",
"@nivo/core": "^0.87.0",
"@nivo/line": "^0.87.0",
"@xyflow/react": "^12.0.4",
"transliteration": "^2.3.5"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const SettingsDevelopersApiKeysNew = lazy(() =>

const SettingsDevelopersWebhooksNew = lazy(() =>
import(
'~/pages/settings/developers/webhooks/SettingsDevelopersWebhooksNew'
'~/pages/settings/developers/webhooks/components/SettingsDevelopersWebhooksNew'
).then((module) => ({
default: module.SettingsDevelopersWebhooksNew,
})),
Expand Down Expand Up @@ -165,7 +165,7 @@ const SettingsObjects = lazy(() =>

const SettingsDevelopersWebhooksDetail = lazy(() =>
import(
'~/pages/settings/developers/webhooks/SettingsDevelopersWebhookDetail'
'~/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail'
).then((module) => ({
default: module.SettingsDevelopersWebhooksDetail,
})),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { webhookGraphDataState } from '@/settings/developers/webhook/states/webhookGraphDataState';
import styled from '@emotion/styled';
import { ResponsiveLine } from '@nivo/line';
import { Section } from '@react-email/components';
import { useRecoilValue } from 'recoil';
import { H2Title } from 'twenty-ui';

export type NivoLineInput = {
id: string | number;
color?: string;
data: Array<{
x: number | string | Date;
y: number | string | Date;
}>;
};
const StyledGraphContainer = styled.div`
height: 200px;
width: 100%;
`;
export const SettingsDeveloppersWebhookUsageGraph = () => {
const webhookGraphData = useRecoilValue(webhookGraphDataState);

return (
<>
{webhookGraphData.length ? (
<Section>
<H2Title title="Statistics" />
<StyledGraphContainer>
<ResponsiveLine
data={webhookGraphData}
colors={(d) => d.color}
margin={{ top: 0, right: 0, bottom: 50, left: 60 }}
xFormat="time:%Y-%m-%d %H:%M%"
xScale={{
type: 'time',
useUTC: false,
format: '%Y-%m-%d %H:%M:%S',
precision: 'hour',
}}
yScale={{
type: 'linear',
}}
axisBottom={{
tickValues: 'every day',
format: '%b %d',
}}
enableTouchCrosshair={true}
enableGridY={false}
enableGridX={false}
enablePoints={false}
/>
</StyledGraphContainer>
</Section>
) : (
<></>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { NivoLineInput } from '@/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph';
import { webhookGraphDataState } from '@/settings/developers/webhook/states/webhookGraphDataState';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';

type SettingsDevelopersWebhookUsageGraphEffectProps = {
webhookId: string;
};

export const SettingsDevelopersWebhookUsageGraphEffect = ({
webhookId,
}: SettingsDevelopersWebhookUsageGraphEffectProps) => {
const setWebhookGraphData = useSetRecoilState(webhookGraphDataState);

const { enqueueSnackBar } = useSnackBar();

useEffect(() => {
const fetchData = async () => {
try {
const queryString = new URLSearchParams({
webhookIdRequest: webhookId,
}).toString();
const token = 'REPLACE_ME';
const response = await fetch(
`https://api.eu-central-1.aws.tinybird.co/v0/pipes/getWebhooksAnalytics.json?${queryString}`,
{
headers: {
Authorization: 'Bearer ' + token,
},
},
);
const result = await response.json();

if (!response.ok) {
enqueueSnackBar('Something went wrong while fetching webhook usage', {
variant: SnackBarVariant.Error,
});
return;
}

const graphInput = result.data
.flatMap(
(dataRow: {
start_interval: string;
failure_count: number;
success_count: number;
}) => [
{
x: dataRow.start_interval,
y: dataRow.failure_count,
id: 'failure_count',
color: 'red',
},
{
x: dataRow.start_interval,
y: dataRow.success_count,
id: 'success_count',
color: 'green',
},
],
)
.reduce(
(
acc: NivoLineInput[],
{
id,
x,
y,
color,
}: { id: string; x: string; y: number; color: string },
) => {
const existingGroupIndex = acc.findIndex(
(group) => group.id === id,
);
const isExistingGroup = existingGroupIndex !== -1;

if (isExistingGroup) {
return acc.map((group, index) =>
index === existingGroupIndex
? { ...group, data: [...group.data, { x, y }] }
: group,
);
} else {
return [...acc, { id, color, data: [{ x, y }] }];
}
},
[],
);
setWebhookGraphData(graphInput);
} catch (error) {
enqueueSnackBar('Something went wrong while fetching webhook usage', {
variant: SnackBarVariant.Error,
});
}
};
fetchData();
}, [enqueueSnackBar, setWebhookGraphData, webhookId]);
return <></>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NivoLineInput } from '@/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph';
import { createState } from 'twenty-ui';

export const webhookGraphDataState = createState<NivoLineInput[]>({
key: 'webhookGraphData',
defaultValue: [],
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export type FeatureFlagKey =
| 'IS_SEARCH_ENABLED'
| 'IS_QUERY_RUNNER_TWENTY_ORM_ENABLED'
| 'IS_GMAIL_SEND_EMAIL_SCOPE_ENABLED'
| 'IS_WORKSPACE_MIGRATED_FOR_SEARCH';
| 'IS_WORKSPACE_MIGRATED_FOR_SEARCH'
| 'IS_ANALYTICS_V2_ENABLED';
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Meta, StoryObj } from '@storybook/react';
import { within } from '@storybook/test';
import { graphql, HttpResponse } from 'msw';

import { SettingsDevelopersWebhooksDetail } from '~/pages/settings/developers/webhooks/SettingsDevelopersWebhookDetail';
import { SettingsDevelopersWebhooksDetail } from '~/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookDetail';
import {
PageDecorator,
PageDecoratorArgs,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Meta, StoryObj } from '@storybook/react';
import { within } from '@storybook/test';

import { SettingsDevelopersWebhooksNew } from '~/pages/settings/developers/webhooks/SettingsDevelopersWebhooksNew';
import { SettingsDevelopersWebhooksNew } from '~/pages/settings/developers/webhooks/components/SettingsDevelopersWebhooksNew';
import {
PageDecorator,
PageDecoratorArgs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { Webhook } from '@/settings/developers/types/webhook/Webhook';
import { SettingsDeveloppersWebhookUsageGraph } from '@/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph';
import { SettingsDevelopersWebhookUsageGraphEffect } from '@/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraphEffect';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { Button } from '@/ui/input/button/components/Button';
Expand All @@ -20,6 +22,7 @@ import { TextInput } from '@/ui/input/components/TextInput';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';

const StyledFilterRow = styled.div`
display: flex;
Expand Down Expand Up @@ -63,6 +66,8 @@ export const SettingsDevelopersWebhooksDetail = () => {
navigate(developerPath);
};

const isAnalyticsV2Enabled = useIsFeatureEnabled('IS_ANALYTICS_V2_ENABLED');

const fieldTypeOptions = [
{ value: '*', label: 'All Objects' },
...objectMetadataItems.map((item) => ({
Expand Down Expand Up @@ -173,6 +178,14 @@ export const SettingsDevelopersWebhooksDetail = () => {
/>
</StyledFilterRow>
</Section>
{isAnalyticsV2Enabled ? (
<>
<SettingsDevelopersWebhookUsageGraphEffect webhookId={webhookId} />
<SettingsDeveloppersWebhookUsageGraph />
</>
) : (
<></>
)}
<Section>
<H2Title title="Danger zone" description="Delete this integration" />
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ export const seedFeatureFlags = async (
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKey.IsAnalyticsV2Enabled,
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKey.IsGmailSendEmailScopeEnabled,
workspaceId: workspaceId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HttpService } from '@nestjs/axios';
import { Logger } from '@nestjs/common';

import { AnalyticsService } from 'src/engine/core-modules/analytics/analytics.service';
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
Expand All @@ -18,17 +19,41 @@ export type CallWebhookJobData = {
@Processor(MessageQueue.webhookQueue)
export class CallWebhookJob {
private readonly logger = new Logger(CallWebhookJob.name);

constructor(private readonly httpService: HttpService) {}
constructor(
private readonly httpService: HttpService,
private readonly analyticsService: AnalyticsService,
) {}

@Process(CallWebhookJob.name)
async handle(data: CallWebhookJobData): Promise<void> {
try {
await this.httpService.axiosRef.post(data.targetUrl, data);
this.logger.log(
`CallWebhookJob successfully called on targetUrl '${data.targetUrl}'`,
const response = await this.httpService.axiosRef.post(
data.targetUrl,
data,
);
const eventInput = {
action: 'webhook.response',
payload: {
status: response.status,
url: data.targetUrl,
webhookId: data.webhookId,
eventName: data.eventName,
},
};

this.analyticsService.create(eventInput, 'webhook', data.workspaceId);
} catch (err) {
const eventInput = {
action: 'webhook.response',
payload: {
status: err.response.status,
url: data.targetUrl,
webhookId: data.webhookId,
eventName: data.eventName,
},
};

this.analyticsService.create(eventInput, 'webhook', data.workspaceId);
this.logger.error(
`Error calling webhook on targetUrl '${data.targetUrl}': ${err}`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CallWebhookJobsJob } from 'src/engine/api/graphql/workspace-query-runne
import { CallWebhookJob } from 'src/engine/api/graphql/workspace-query-runner/jobs/call-webhook.job';
import { RecordPositionBackfillJob } from 'src/engine/api/graphql/workspace-query-runner/jobs/record-position-backfill.job';
import { RecordPositionBackfillModule } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-module';
import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';

Expand All @@ -14,6 +15,7 @@ import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/works
DataSourceModule,
RecordPositionBackfillModule,
HttpModule,
AnalyticsModule,
],
providers: [CallWebhookJobsJob, CallWebhookJob, RecordPositionBackfillJob],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export enum FeatureFlagKey {
IsSearchEnabled = 'IS_SEARCH_ENABLED',
IsWorkspaceMigratedForSearch = 'IS_WORKSPACE_MIGRATED_FOR_SEARCH',
IsGmailSendEmailScopeEnabled = 'IS_GMAIL_SEND_EMAIL_SCOPE_ENABLED',
IsAnalyticsV2Enabled = 'IS_ANALYTICS_V2_ENABLED',
}
Loading

0 comments on commit f901512

Please sign in to comment.