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

[SFT-1608]: Write integration for Form Monitor #1686

Open
wants to merge 22 commits into
base: v5
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
759439b
feat(SFT-1608): adding initial Form Monitor files
gustavs-gutmanis Dec 2, 2024
5754088
feat(SFT-1608): add form monitor integration
gustavs-gutmanis Dec 3, 2024
97236d7
chore: updating artifacts
gustavs-gutmanis Dec 3, 2024
6cf0010
feat(SFT-1608): adding form manifest
gustavs-gutmanis Dec 4, 2024
fc9732a
feat(SFT-1608): sending URL with manifest
gustavs-gutmanis Dec 5, 2024
9881a73
feat(SFT-1608): adding captcha bypass
gustavs-gutmanis Dec 9, 2024
7d31d17
feat(SFT-1608): add hidden submission support for form monitor checking
gustavs-gutmanis Dec 9, 2024
6189c60
feat(SFT-1608): adding initial Form Monitor files
gustavs-gutmanis Dec 2, 2024
8afd700
feat(SFT-1608): add form monitor integration
gustavs-gutmanis Dec 3, 2024
621521f
chore: updating artifacts
gustavs-gutmanis Dec 3, 2024
c2ebc7d
feat(SFT-1608): adding form manifest
gustavs-gutmanis Dec 4, 2024
edb32b4
feat(SFT-1608): sending URL with manifest
gustavs-gutmanis Dec 5, 2024
29c6750
feat(SFT-1608): adding captcha bypass
gustavs-gutmanis Dec 9, 2024
d0d1682
feat(SFT-1608): add hidden submission support for form monitor checking
gustavs-gutmanis Dec 9, 2024
58d0cf0
Merge remote-tracking branch 'origin/feat/SFT-1608-form-monitor' into…
gustavs-gutmanis Dec 9, 2024
9064487
feat(SFT-1608): disabling integrations for form monitor requests
gustavs-gutmanis Dec 9, 2024
a8171a9
feat(SFT-1611): override all emails when submitting through FM
gustavs-gutmanis Dec 11, 2024
9e5b515
feat(SFT-1613): build UI for Form Monitor
gustavs-gutmanis Dec 13, 2024
9c76bfa
Merge branch 'v5' into feat/SFT-1608-form-monitor
gustavs-gutmanis Dec 16, 2024
b2d98cd
Merge branch 'v5' of https://github.com/solspace/craft-freeform into …
kjmartens Dec 31, 2024
9a5ad0f
Merge branch 'v5' of https://github.com/solspace/craft-freeform into …
kjmartens Dec 31, 2024
f089b64
feat(SFT-1608): add submission ack call
gustavs-gutmanis Jan 2, 2025
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: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"fix:dry-run": "vendor/bin/php-cs-fixer fix --dry-run --diff --config=./.php-cs-fixer.dist.php"
},
"extra": {
"schemaVersion": "5.4.0",
"schemaVersion": "5.5.0",
"handle": "freeform",
"class": "Solspace\\Freeform\\Freeform",
"name": "Freeform",
Expand Down
13 changes: 13 additions & 0 deletions packages/client/src/app/pages/form-monitor/form-monitor.empty.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import { EmptyBlock } from '@components/empty-block/empty-block';
import translate from '@ff-client/utils/translations';

import { FormMonitorWrapper } from './form-monitor.styles';

export const FMEmptyTests: React.FC = () => {
return (
<FormMonitorWrapper>
<EmptyBlock lite title={translate('No form tests found')} />
</FormMonitorWrapper>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export const FMFormsList: React.FC = () => {
return <div></div>;
};
41 changes: 41 additions & 0 deletions packages/client/src/app/pages/form-monitor/form-monitor.queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { UseQueryResult } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';

export const QKFormMonitor = {
forms: ['form-monitor', 'forms'],
formTests: (id?: number) =>
id !== undefined
? [...QKFormMonitor.forms, id, 'form-tests']
: [...QKFormMonitor.forms, 'tests'],
} as const;

export const useFMForms = (): UseQueryResult<number[]> => {
return useQuery(QKFormMonitor.forms, () =>
axios.get<number[]>('/api/form-monitor/forms').then((res) => res.data)
);
};

type FMTest = {
id: string;
formId: number;
dateAttempted: string;
dateCompleted: string;
status: 'success' | 'fail';
response: null | string;
responseCode: number;
};

type FMTestsResponse = FMTest[];

export const useFMFormTestsQuery = (
id?: number
): UseQueryResult<FMTestsResponse> => {
const url = id
? `/api/form-monitor/forms/${id}/tests`
: '/api/form-monitor/tests';

return useQuery(QKFormMonitor.formTests(id), () =>
axios.get<FMTestsResponse>(url).then((res) => res.data)
);
};
69 changes: 69 additions & 0 deletions packages/client/src/app/pages/form-monitor/form-monitor.styles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { PropsWithChildren } from 'react';
import React from 'react';
import { Breadcrumb } from '@components/breadcrumbs/breadcrumbs';
import { ContentContainer } from '@components/layout/blocks/content-container';
import { HeaderContainer } from '@components/layout/blocks/header-container';
import { colors } from '@ff-client/styles/variables';
import translate from '@ff-client/utils/translations';
import styled from 'styled-components';

export const FormMonitorWrapper: React.FC<PropsWithChildren> = ({
children,
}) => {
return (
<div>
<Breadcrumb
id="form-monitor"
label={translate('Form Monitor')}
url="/form-monitor"
/>

<HeaderContainer>{translate('Form Monitor')}</HeaderContainer>

<div id="main-content">
<ContentContainer>
<div id="content">{children}</div>
</ContentContainer>
</div>
</div>
);
};

export const TestBlock = styled.div`
display: flex;
gap: 5px;
`;

export const TestTable = styled.table`
thead th {
white-space: nowrap;
}

tbody td {
vertical-align: top;

&.no-break {
white-space: nowrap;
}
}

.status-col {
display: flex;
gap: 5px;
align-items: center;
}
`;

export const StatusBadge = styled.div`
padding: 1px 5px;

border-radius: 5px;

font-size: 10px;
color: ${colors.white};
background-color: ${colors.teal500};

&.status-fail {
background-color: ${colors.red500};
}
`;
92 changes: 92 additions & 0 deletions packages/client/src/app/pages/form-monitor/form-monitor.tests.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React from 'react';
import { Link, useParams } from 'react-router-dom';
import { useSidebarSelect } from '@ff-client/hooks/use-sidebar-select';
import { useQueryFormsWithStats } from '@ff-client/queries/forms';
import { scrollBar } from '@ff-client/styles/mixins';
import { borderRadius, colors, spacings } from '@ff-client/styles/variables';
import translate from '@ff-client/utils/translations';
import { format, parseISO } from 'date-fns';
import styled from 'styled-components';

import { FMEmptyTests } from './form-monitor.empty';
import { useFMFormTestsQuery } from './form-monitor.queries';
import {
FormMonitorWrapper,
StatusBadge,
TestTable,
} from './form-monitor.styles';

const CodeBlock = styled.div`
position: relative;

padding: ${spacings.sm} ${spacings.md};

font-family: monospace;

background: ${colors.gray050};
border: 1px solid ${colors.hairline};
border-radius: ${borderRadius.lg};

max-height: 60px;
overflow-y: auto;

${scrollBar};
`;

export const FMTests: React.FC = () => {
const { formId } = useParams();

const { data: forms } = useQueryFormsWithStats();
const { data, isFetching } = useFMFormTestsQuery(Number(formId));

useSidebarSelect(5);

return (
<FormMonitorWrapper>
{data === undefined && isFetching && <div>{translate('Loading...')}</div>}
{data && data.length === 0 && <FMEmptyTests />}
{data !== undefined && data.length > 0 && (
<TestTable>
<thead>
<tr>
<th>{translate('Test ID')}</th>
<th>{translate('Date')}</th>
<th>{translate('Form')}</th>
<th>{translate('Status')}</th>
<th>{translate('Response')}</th>
</tr>
</thead>
<tbody>
{data.map((test) => {
const form = forms.find((form) => form.id === test.formId);
if (!form) {
return null;
}

return (
<tr key={test.id}>
<td className="no-break">#{test.id}</td>
<td className="no-break" title={test.dateCompleted}>
{format(parseISO(test.dateCompleted), 'do MMM yyyy')}
</td>
<td>
<Link to={`/forms/${form.id}`}>{form.name}</Link>
</td>
<td className="status-col no-break">
<StatusBadge className={`status-${test.status}`}>
{test.responseCode}
</StatusBadge>
<div>{test.status}</div>
</td>
<td className="code" title={test.response}>
{!!test.response && <CodeBlock>{test.response}</CodeBlock>}
</td>
</tr>
);
})}
</tbody>
</TestTable>
)}
</FormMonitorWrapper>
);
};
62 changes: 62 additions & 0 deletions packages/client/src/app/pages/form-monitor/form-monitor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* eslint-disable react/display-name */
import React from 'react';
import { Link } from 'react-router-dom';
import { EmptyBlock } from '@components/empty-block/empty-block';
import config, { Edition } from '@config/freeform/freeform.config';
import { useSidebarSelect } from '@ff-client/hooks/use-sidebar-select';
import { useQueryFormsWithStats } from '@ff-client/queries/forms';
import translate from '@ff-client/utils/translations';

import { useFMForms } from './form-monitor.queries';
import { FormMonitorWrapper } from './form-monitor.styles';

export const FormMonitor: React.FC = () => {
const isPro = config.editions.isAtLeast(Edition.Pro);
useSidebarSelect(5);

const { data: forms, isFetching: isFetchingForms } = useQueryFormsWithStats();
const { data: formIds, isFetching: isFetchingFormids } = useFMForms();
Copy link
Contributor

Choose a reason for hiding this comment

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

isFetchingFormids --> isFetchingFormIds

Copy link
Contributor

Choose a reason for hiding this comment

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

Also not liking the use of FM instead of FormMonitor throughout the API

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good pointers, I'll change them


const isLoading =
(!forms || !formIds) && (isFetchingForms || isFetchingFormids);

if (!isPro) {
return (
<FormMonitorWrapper>
<EmptyBlock
lite
title={translate(
'Upgrade to the Freeform Pro edition to get access to Form Monitor'
)}
/>
</FormMonitorWrapper>
);
}

if (isLoading) {
return (
<FormMonitorWrapper>
<div>{translate('Loading...')}</div>
</FormMonitorWrapper>
);
}

return (
<FormMonitorWrapper>
<ul>
{formIds.map((id) => {
const form = forms.find((form) => form.id === id);
if (!form) {
return null;
}

return (
<li key={id}>
<Link to={`${id}/tests`}>{form.name}</Link>
</li>
);
})}
</ul>
</FormMonitorWrapper>
);
};
2 changes: 1 addition & 1 deletion packages/client/src/app/pages/import-export/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ImportExportWrapper } from './index.styles';

export const ImportExport: React.FC = () => {
const { pathname } = useLocation();
useSidebarSelect(4);
useSidebarSelect(3);
useQueryFormsWithStats();

let title: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const LimitedUsers: React.FC = () => {
const isPro = config.editions.isAtLeast(Edition.Pro);
const isCraft5 = config.metadata.craft.is5;

useSidebarSelect(5);
useSidebarSelect(4);

if (!data && isFetching) {
return <div>Loading...</div>;
Expand Down
6 changes: 6 additions & 0 deletions packages/client/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

import '../config';

import { FormMonitor } from './app/pages/form-monitor/form-monitor';
import { FMTests } from './app/pages/form-monitor/form-monitor.tests';
import { Form, Forms } from './app/pages/forms';
import { ImportExport } from './app/pages/import-export';
import { ExportFreeform } from './app/pages/import-export/export/views/freeform/freeform';
Expand Down Expand Up @@ -95,6 +97,10 @@ root.render(
/>
<Route index element={<LimitedUsers />} />
</Route>
<Route path="form-monitor">
<Route index element={<FormMonitor />} />
<Route path=":formId/tests" element={<FMTests />} />
</Route>
</Route>
</Routes>
</ModalProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ public function getRecordsByForm(?Form $form = null): array
;
}

public function getByFormAndId(Form $form, int $id): ?NotificationInterface
{
$record = FormNotificationRecord::findOne([
'formId' => $form->getId(),
'id' => $id,
]);

return $record ? $this->createNotificationObjects($record) : null;
}

public function getByForm(?Form $form = null): array
{
$records = $this->getRecordsByForm($form);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public function sendToRecipients(SendNotificationsEvent $event): void
'postedData' => $postedData,
'recipients' => $recipients,
'template' => $template,
'notificationType' => Admin::class,
])
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public function sendToRecipients(SendNotificationsEvent $event): void
'postedData' => $postedData,
'recipients' => $recipients,
'template' => $template,
'notificationType' => Conditional::class,
])
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public function sendToRecipients(SendNotificationsEvent $event): void
'postedData' => $postedData,
'recipients' => $recipients,
'template' => $template,
'notificationType' => Dynamic::class,
])
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public function sendToRecipients(SendNotificationsEvent $event): void
'postedData' => $postedData,
'recipients' => $recipientCollection,
'template' => $notificationTemplate,
'notificationType' => 'DynamicTemplate',
])
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public function sendToRecipients(SendNotificationsEvent $event): void
'postedData' => $postedData,
'recipients' => $recipientCollection,
'template' => $notificationTemplate,
'notificationType' => EmailField::class,
])
);
}
Expand Down
Loading