diff --git a/app/scenes/Settings/SelfHosted.tsx b/app/scenes/Settings/SelfHosted.tsx index 45c8cb38f..ce56859da 100644 --- a/app/scenes/Settings/SelfHosted.tsx +++ b/app/scenes/Settings/SelfHosted.tsx @@ -16,6 +16,7 @@ import SettingRow from "./components/SettingRow"; type FormData = { drawIoUrl: string; + gristUrl: string; }; function SelfHosted() { @@ -23,11 +24,16 @@ function SelfHosted() { const { t } = useTranslation(); const { showToast } = useToasts(); - const integration = find(integrations.orderedData, { + const integrationDiagrams = find(integrations.orderedData, { type: IntegrationType.Embed, service: IntegrationService.Diagrams, }) as Integration | undefined; + const integrationGrist = find(integrations.orderedData, { + type: IntegrationType.Embed, + service: IntegrationService.Grist, + }) as Integration | undefined; + const { register, reset, @@ -36,7 +42,8 @@ function SelfHosted() { } = useForm({ mode: "all", defaultValues: { - drawIoUrl: integration?.settings.url, + drawIoUrl: integrationDiagrams?.settings.url, + gristUrl: integrationGrist?.settings.url, }, }); @@ -47,15 +54,18 @@ function SelfHosted() { }, [integrations]); React.useEffect(() => { - reset({ drawIoUrl: integration?.settings.url }); - }, [integration, reset]); + reset({ + drawIoUrl: integrationDiagrams?.settings.url, + gristUrl: integrationGrist?.settings.url, + }); + }, [integrationDiagrams, integrationGrist, reset]); const handleSubmit = React.useCallback( async (data: FormData) => { try { if (data.drawIoUrl) { await integrations.save({ - id: integration?.id, + id: integrationDiagrams?.id, type: IntegrationType.Embed, service: IntegrationService.Diagrams, settings: { @@ -63,7 +73,20 @@ function SelfHosted() { }, }); } else { - await integration?.delete(); + await integrationDiagrams?.delete(); + } + + if (data.gristUrl) { + await integrations.save({ + id: integrationGrist?.id, + type: IntegrationType.Embed, + service: IntegrationService.Grist, + settings: { + url: data.gristUrl, + }, + }); + } else { + await integrationGrist?.delete(); } showToast(t("Settings saved"), { @@ -75,7 +98,7 @@ function SelfHosted() { }); } }, - [integrations, integration, t, showToast] + [integrations, integrationDiagrams, integrationGrist, t, showToast] ); return ( @@ -98,6 +121,19 @@ function SelfHosted() { /> + + + + diff --git a/server/models/Integration.ts b/server/models/Integration.ts index 1d188e9a9..4906cc8bd 100644 --- a/server/models/Integration.ts +++ b/server/models/Integration.ts @@ -18,6 +18,7 @@ import Fix from "./decorators/Fix"; export enum UserCreatableIntegrationService { Diagrams = "diagrams", + Grist = "grist", GoogleAnalytics = "google-analytics", } diff --git a/server/routes/api/integrations/integrations.test.ts b/server/routes/api/integrations/integrations.test.ts index 792e83abb..7ef32d690 100644 --- a/server/routes/api/integrations/integrations.test.ts +++ b/server/routes/api/integrations/integrations.test.ts @@ -49,7 +49,7 @@ describe("#integrations.update", () => { expect(res.status).toEqual(403); }); - it("should succeed with status 200 ok when settings are updated", async () => { + it("should succeed with status 200 ok when diagram integration settings are updated", async () => { const admin = await buildAdmin(); const integration = await buildIntegration({ @@ -72,6 +72,30 @@ describe("#integrations.update", () => { expect(body.data.id).toEqual(integration.id); expect(body.data.settings.url).toEqual("https://foo.bar"); }); + + it("should succeed with status 200 ok when grist integration settings are updated", async () => { + const admin = await buildAdmin(); + + const integration = await buildIntegration({ + userId: admin.id, + teamId: admin.teamId, + service: IntegrationService.Grist, + type: IntegrationType.Embed, + settings: { url: "https://example.com" }, + }); + + const res = await server.post("/api/integrations.update", { + body: { + token: admin.getJwtToken(), + id: integration.id, + settings: { url: "https://grist.example.com" }, + }, + }); + + const body = await res.json(); + expect(body.data.id).toEqual(integration.id); + expect(body.data.settings.url).toEqual("https://grist.example.com"); + }); }); describe("#integrations.create", () => { @@ -111,6 +135,25 @@ describe("#integrations.create", () => { expect(body.data.settings).not.toBeFalsy(); expect(body.data.settings.measurementId).toEqual("123"); }); + + it("should succeed with status 200 ok for an grist integration", async () => { + const admin = await buildAdmin(); + + const res = await server.post("/api/integrations.create", { + body: { + token: admin.getJwtToken(), + type: IntegrationType.Embed, + service: UserCreatableIntegrationService.Grist, + settings: { url: "https://grist.example.com" }, + }, + }); + const body = await res.json(); + expect(res.status).toEqual(200); + expect(body.data.type).toEqual(IntegrationType.Embed); + expect(body.data.service).toEqual(UserCreatableIntegrationService.Grist); + expect(body.data.settings).not.toBeFalsy(); + expect(body.data.settings.url).toEqual("https://grist.example.com"); + }); }); describe("#integrations.delete", () => { diff --git a/shared/editor/embeds/Grist.tsx b/shared/editor/embeds/Grist.tsx index 5f6c1972e..851ee0d10 100644 --- a/shared/editor/embeds/Grist.tsx +++ b/shared/editor/embeds/Grist.tsx @@ -24,4 +24,6 @@ function Grist(props: Props) { Grist.ENABLED = [new RegExp("^https?://([a-z.-]+\\.)?getgrist\\.com/(.+)$")]; +Grist.URL_PATH_REGEX = /(.+)/; + export default Grist; diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index 513c6c2b4..dc3634231 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -851,6 +851,8 @@ "Allow members to create new collections within the knowledge base": "Allow members to create new collections within the knowledge base", "Draw.io deployment": "Draw.io deployment", "Add your self-hosted draw.io installation url here to enable automatic embedding of diagrams within documents.": "Add your self-hosted draw.io installation url here to enable automatic embedding of diagrams within documents.", + "Grist deployment": "Grist deployment", + "Add your self-hosted grist installation URL here.": "Add your self-hosted grist installation URL here.", "Sharing is currently disabled.": "Sharing is currently disabled.", "You can globally enable and disable public document sharing in the security settings.": "You can globally enable and disable public document sharing in the security settings.", "Documents that have been shared are listed below. Anyone that has the public link can access a read-only version of the document until the link has been revoked.": "Documents that have been shared are listed below. Anyone that has the public link can access a read-only version of the document until the link has been revoked.", diff --git a/shared/types.ts b/shared/types.ts index a2af6fd22..1d32d4065 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -79,6 +79,7 @@ export enum IntegrationType { export enum IntegrationService { Diagrams = "diagrams", + Grist = "grist", Slack = "slack", GoogleAnalytics = "google-analytics", }