Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add webhook response graph from the last 5 days #7487

Merged
merged 7 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a usage in ActivityLog.tsx

"@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
4 changes: 2 additions & 2 deletions packages/twenty-front/src/SettingsRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,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 @@ -164,7 +164,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';
Dismissed Show dismissed Hide dismissed
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 { createState } from 'twenty-ui';
import { NivoLineInput } from '~/pages/settings/developers/webhooks/components/SettingsDevelopersWebhookUsageGraph';

export const webhookGraphDataState = createState<NivoLineInput[]>({
key: 'webhookGraphData',
defaultValue: [],
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export type FeatureFlagKey =
| 'IS_WORKSPACE_FAVORITE_ENABLED'
| 'IS_SEARCH_ENABLED'
| 'IS_QUERY_RUNNER_TWENTY_ORM_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 @@ -174,6 +179,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: false,
},
])
.execute();
};
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,
},
};
Comment on lines 45 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider wrapping err.response.status in a try-catch block, as err.response might be undefined for network errors


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 @@ -12,4 +12,5 @@ export enum FeatureFlagKey {
IsWorkspaceFavoriteEnabled = 'IS_WORKSPACE_FAVORITE_ENABLED',
IsSearchEnabled = 'IS_SEARCH_ENABLED',
IsWorkspaceMigratedForSearch = 'IS_WORKSPACE_MIGRATED_FOR_SEARCH',
IsAnalyticsV2Enabled = 'IS_ANALYTICS_V2_ENABLED',
}
Loading
Loading