From 4110f610a033ec8125d901846d46e75aa5000809 Mon Sep 17 00:00:00 2001
From: Valentino Hudhra <v.hudhra@gmail.com>
Date: Mon, 7 Oct 2024 15:47:14 +0200
Subject: [PATCH 1/6] googleanalytics

---
 .../googleanalytics/gitbook-manifest.yaml     |  8 +-
 .../googleanalytics/src/components.tsx        | 83 +++++++++++++++++++
 integrations/googleanalytics/src/index.ts     |  2 +
 3 files changed, 86 insertions(+), 7 deletions(-)
 create mode 100644 integrations/googleanalytics/src/components.tsx

diff --git a/integrations/googleanalytics/gitbook-manifest.yaml b/integrations/googleanalytics/gitbook-manifest.yaml
index cc77c2d2e..c103347e6 100644
--- a/integrations/googleanalytics/gitbook-manifest.yaml
+++ b/integrations/googleanalytics/gitbook-manifest.yaml
@@ -53,10 +53,4 @@ categories:
     - analytics
 configurations:
     site:
-        properties:
-            tracking_id:
-                type: string
-                title: Tracking ID
-                description: Look for this in your Google Analytics account.
-        required:
-            - tracking_id
+        componentId: config
diff --git a/integrations/googleanalytics/src/components.tsx b/integrations/googleanalytics/src/components.tsx
new file mode 100644
index 000000000..bf3d76c3a
--- /dev/null
+++ b/integrations/googleanalytics/src/components.tsx
@@ -0,0 +1,83 @@
+import { createComponent, RuntimeContext, RuntimeEnvironment } from '@gitbook/runtime';
+
+type GARuntimeEnvironment = RuntimeEnvironment<{}, GASiteInstallationConfiguration>;
+
+type GARuntimeContext = RuntimeContext<GARuntimeEnvironment>;
+
+type GASiteInstallationConfiguration = {
+	tracking_id?: string;
+};
+
+type GAState = GASiteInstallationConfiguration;
+
+type GAProps = {
+	siteInstallation: {
+		configuration?: GASiteInstallationConfiguration;
+	};
+};
+
+export type GAAction = { action: 'save.config' };
+
+export const configBlock = createComponent<GAProps, GAState, GAAction, GARuntimeContext>({
+	componentId: 'config',
+	initialState: (props) => {
+		const siteInstallation = props.siteInstallation;
+		return {
+			tracking_id: siteInstallation.configuration?.tracking_id ?? '',
+		};
+	},
+	action: async (element, action, context) => {
+		switch (action.action) {
+			case 'save.config':
+				const { api, environment } = context;
+				const siteInstallation = environment.siteInstallation;
+				if (!siteInstallation) {
+					throw Error('No site installation found');
+				}
+
+				const configurationBody = {
+					...siteInstallation.configuration,
+					tracking_id: element.state.tracking_id,
+				};
+				await api.integrations.updateIntegrationSiteInstallation(
+					siteInstallation.integration,
+					siteInstallation.installation,
+					siteInstallation.site,
+					{
+						configuration: {
+							...configurationBody,
+						},
+					},
+				);
+				return element;
+		}
+	},
+	render: async () => {
+		return (
+			<configuration>
+				<input
+					label="Client ID"
+					hint={<text>The unique identifier of your GA app client.</text>}
+					element={<textinput state="tracking_id" placeholder="Tracking ID" />}
+				/>
+
+				<divider />
+				<input
+					label=""
+					hint=""
+					element={
+						<button
+							style="primary"
+							disabled={false}
+							label="Save"
+							tooltip="Save configuration"
+							onPress={{
+								action: 'save.config',
+							}}
+						/>
+					}
+				/>
+			</configuration>
+		);
+	},
+});
diff --git a/integrations/googleanalytics/src/index.ts b/integrations/googleanalytics/src/index.ts
index 40a6dfb7d..8adef5b2f 100644
--- a/integrations/googleanalytics/src/index.ts
+++ b/integrations/googleanalytics/src/index.ts
@@ -5,6 +5,7 @@ import {
     RuntimeEnvironment,
 } from '@gitbook/runtime';
 
+import { configBlock } from './components';
 import script from './script.raw.js';
 
 type GARuntimeContext = RuntimeContext<
@@ -35,4 +36,5 @@ export const handleFetchEvent: FetchPublishScriptEventCallback = async (
 
 export default createIntegration<GARuntimeContext>({
     fetch_published_script: handleFetchEvent,
+    components: [configBlock],
 });

From cdf7a8af27213fadb582c3b5dd3225b5feb0498a Mon Sep 17 00:00:00 2001
From: Valentino Hudhra <v.hudhra@gmail.com>
Date: Mon, 7 Oct 2024 17:30:16 +0200
Subject: [PATCH 2/6] format

---
 .../googleanalytics/src/components.tsx        | 126 +++++++++---------
 1 file changed, 63 insertions(+), 63 deletions(-)

diff --git a/integrations/googleanalytics/src/components.tsx b/integrations/googleanalytics/src/components.tsx
index bf3d76c3a..1ee76982b 100644
--- a/integrations/googleanalytics/src/components.tsx
+++ b/integrations/googleanalytics/src/components.tsx
@@ -5,79 +5,79 @@ type GARuntimeEnvironment = RuntimeEnvironment<{}, GASiteInstallationConfigurati
 type GARuntimeContext = RuntimeContext<GARuntimeEnvironment>;
 
 type GASiteInstallationConfiguration = {
-	tracking_id?: string;
+    tracking_id?: string;
 };
 
 type GAState = GASiteInstallationConfiguration;
 
 type GAProps = {
-	siteInstallation: {
-		configuration?: GASiteInstallationConfiguration;
-	};
+    siteInstallation: {
+        configuration?: GASiteInstallationConfiguration;
+    };
 };
 
 export type GAAction = { action: 'save.config' };
 
 export const configBlock = createComponent<GAProps, GAState, GAAction, GARuntimeContext>({
-	componentId: 'config',
-	initialState: (props) => {
-		const siteInstallation = props.siteInstallation;
-		return {
-			tracking_id: siteInstallation.configuration?.tracking_id ?? '',
-		};
-	},
-	action: async (element, action, context) => {
-		switch (action.action) {
-			case 'save.config':
-				const { api, environment } = context;
-				const siteInstallation = environment.siteInstallation;
-				if (!siteInstallation) {
-					throw Error('No site installation found');
-				}
+    componentId: 'config',
+    initialState: (props) => {
+        const siteInstallation = props.siteInstallation;
+        return {
+            tracking_id: siteInstallation.configuration?.tracking_id ?? '',
+        };
+    },
+    action: async (element, action, context) => {
+        switch (action.action) {
+            case 'save.config':
+                const { api, environment } = context;
+                const siteInstallation = environment.siteInstallation;
+                if (!siteInstallation) {
+                    throw Error('No site installation found');
+                }
 
-				const configurationBody = {
-					...siteInstallation.configuration,
-					tracking_id: element.state.tracking_id,
-				};
-				await api.integrations.updateIntegrationSiteInstallation(
-					siteInstallation.integration,
-					siteInstallation.installation,
-					siteInstallation.site,
-					{
-						configuration: {
-							...configurationBody,
-						},
-					},
-				);
-				return element;
-		}
-	},
-	render: async () => {
-		return (
-			<configuration>
-				<input
-					label="Client ID"
-					hint={<text>The unique identifier of your GA app client.</text>}
-					element={<textinput state="tracking_id" placeholder="Tracking ID" />}
-				/>
+                const configurationBody = {
+                    ...siteInstallation.configuration,
+                    tracking_id: element.state.tracking_id,
+                };
+                await api.integrations.updateIntegrationSiteInstallation(
+                    siteInstallation.integration,
+                    siteInstallation.installation,
+                    siteInstallation.site,
+                    {
+                        configuration: {
+                            ...configurationBody,
+                        },
+                    },
+                );
+                return element;
+        }
+    },
+    render: async () => {
+        return (
+            <configuration>
+                <input
+                    label="Client ID"
+                    hint={<text>The unique identifier of your GA app client.</text>}
+                    element={<textinput state="tracking_id" placeholder="Tracking ID" />}
+                />
 
-				<divider />
-				<input
-					label=""
-					hint=""
-					element={
-						<button
-							style="primary"
-							disabled={false}
-							label="Save"
-							tooltip="Save configuration"
-							onPress={{
-								action: 'save.config',
-							}}
-						/>
-					}
-				/>
-			</configuration>
-		);
-	},
+                <divider />
+                <input
+                    label=""
+                    hint=""
+                    element={
+                        <button
+                            style="primary"
+                            disabled={false}
+                            label="Save"
+                            tooltip="Save configuration"
+                            onPress={{
+                                action: 'save.config',
+                            }}
+                        />
+                    }
+                />
+            </configuration>
+        );
+    },
 });

From d5f65fc0f733bf7c6eb1a5d112c6508a763391bf Mon Sep 17 00:00:00 2001
From: Valentino Hudhra <v.hudhra@gmail.com>
Date: Fri, 11 Oct 2024 09:49:54 +0200
Subject: [PATCH 3/6] Render outputs commit

---
 packages/runtime/src/components.ts | 51 ++++++++++++++++++++----------
 1 file changed, 34 insertions(+), 17 deletions(-)

diff --git a/packages/runtime/src/components.ts b/packages/runtime/src/components.ts
index 64e4b0735..963c30c1b 100644
--- a/packages/runtime/src/components.ts
+++ b/packages/runtime/src/components.ts
@@ -1,9 +1,9 @@
 import {
     ContentKitBlock,
-    UIRenderEvent,
-    ContentKitRenderOutput,
     ContentKitContext,
     ContentKitDefaultAction,
+    ContentKitRenderOutput,
+    UIRenderEvent,
 } from '@gitbook/api';
 
 import { RuntimeCallback, RuntimeContext } from './context';
@@ -67,15 +67,19 @@ export function createComponent<
      * Initial state of the component.
      */
     initialState?:
-        | State
-        | ((props: Props, renderContext: ContentKitContext, context: Context) => State);
+    | State
+    | ((props: Props, renderContext: ContentKitContext, context: Context) => State);
 
     /**
      * Callback to handle a dispatched action.
      */
     action?: RuntimeCallback<
         [ComponentInstance<Props, State>, ComponentAction<Action>],
-        Promise<{ props?: Props; state?: State } | undefined>,
+        Promise<
+            | { type?: 'element'; props?: Props; state?: State }
+            | { type: 'complete'; returnValue?: PlainObject }
+            | undefined
+        >,
         Context
     >;
 
@@ -112,29 +116,42 @@ export function createComponent<
                 dynamicState: (key) => ({ $state: key }),
             };
 
+            const wrapResponse = (output: ContentKitRenderOutput) => {
+                return new Response(JSON.stringify(output), {
+                    headers: {
+                        'Content-Type': 'application/json',
+                        ...(cache
+                            ? {
+                                // @ts-ignore - I'm not sure how to fix this one with TS
+                                'Cache-Control': `max-age=${cache.maxAge}`,
+                            }
+                            : {}),
+                    },
+                });
+            };
+
             if (action && component.action) {
-                instance = { ...instance, ...(await component.action(instance, action, context)) };
+                const actionResult = await component.action(instance, action, context);
+
+                // If the action is complete, return the result directly. No need to render the component.
+                if (actionResult?.type === 'complete') {
+                    return wrapResponse(actionResult);
+                }
+
+                instance = { ...instance, ...actionResult };
             }
 
             const element = await component.render(instance, context);
 
             const output: ContentKitRenderOutput = {
+                // for backward compatibility always default to 'element'
+                type: 'element',
                 state: instance.state,
                 props: instance.props,
                 element,
             };
 
-            return new Response(JSON.stringify(output), {
-                headers: {
-                    'Content-Type': 'application/json',
-                    ...(cache
-                        ? {
-                              // @ts-ignore - I'm not sure how to fix this one with TS
-                              'Cache-Control': `max-age=${cache.maxAge}`,
-                          }
-                        : {}),
-                },
-            });
+            return wrapResponse(output);
         },
     };
 }

From 81265a6b7d762cd9e6f0f125792839252b6338ff Mon Sep 17 00:00:00 2001
From: Valentino Hudhra <v.hudhra@gmail.com>
Date: Fri, 11 Oct 2024 13:58:44 +0200
Subject: [PATCH 4/6] revert runtime changes

---
 packages/runtime/src/components.ts | 51 ++++++++++--------------------
 1 file changed, 17 insertions(+), 34 deletions(-)

diff --git a/packages/runtime/src/components.ts b/packages/runtime/src/components.ts
index 963c30c1b..64e4b0735 100644
--- a/packages/runtime/src/components.ts
+++ b/packages/runtime/src/components.ts
@@ -1,9 +1,9 @@
 import {
     ContentKitBlock,
+    UIRenderEvent,
+    ContentKitRenderOutput,
     ContentKitContext,
     ContentKitDefaultAction,
-    ContentKitRenderOutput,
-    UIRenderEvent,
 } from '@gitbook/api';
 
 import { RuntimeCallback, RuntimeContext } from './context';
@@ -67,19 +67,15 @@ export function createComponent<
      * Initial state of the component.
      */
     initialState?:
-    | State
-    | ((props: Props, renderContext: ContentKitContext, context: Context) => State);
+        | State
+        | ((props: Props, renderContext: ContentKitContext, context: Context) => State);
 
     /**
      * Callback to handle a dispatched action.
      */
     action?: RuntimeCallback<
         [ComponentInstance<Props, State>, ComponentAction<Action>],
-        Promise<
-            | { type?: 'element'; props?: Props; state?: State }
-            | { type: 'complete'; returnValue?: PlainObject }
-            | undefined
-        >,
+        Promise<{ props?: Props; state?: State } | undefined>,
         Context
     >;
 
@@ -116,42 +112,29 @@ export function createComponent<
                 dynamicState: (key) => ({ $state: key }),
             };
 
-            const wrapResponse = (output: ContentKitRenderOutput) => {
-                return new Response(JSON.stringify(output), {
-                    headers: {
-                        'Content-Type': 'application/json',
-                        ...(cache
-                            ? {
-                                // @ts-ignore - I'm not sure how to fix this one with TS
-                                'Cache-Control': `max-age=${cache.maxAge}`,
-                            }
-                            : {}),
-                    },
-                });
-            };
-
             if (action && component.action) {
-                const actionResult = await component.action(instance, action, context);
-
-                // If the action is complete, return the result directly. No need to render the component.
-                if (actionResult?.type === 'complete') {
-                    return wrapResponse(actionResult);
-                }
-
-                instance = { ...instance, ...actionResult };
+                instance = { ...instance, ...(await component.action(instance, action, context)) };
             }
 
             const element = await component.render(instance, context);
 
             const output: ContentKitRenderOutput = {
-                // for backward compatibility always default to 'element'
-                type: 'element',
                 state: instance.state,
                 props: instance.props,
                 element,
             };
 
-            return wrapResponse(output);
+            return new Response(JSON.stringify(output), {
+                headers: {
+                    'Content-Type': 'application/json',
+                    ...(cache
+                        ? {
+                              // @ts-ignore - I'm not sure how to fix this one with TS
+                              'Cache-Control': `max-age=${cache.maxAge}`,
+                          }
+                        : {}),
+                },
+            });
         },
     };
 }

From 9d94330bfd03b32b324e8e26fcff1fbf9d8e73ea Mon Sep 17 00:00:00 2001
From: Valentino Hudhra <v.hudhra@gmail.com>
Date: Fri, 11 Oct 2024 14:03:29 +0200
Subject: [PATCH 5/6] changeset

---
 .changeset/slow-keys-confess.md | 5 +++++
 1 file changed, 5 insertions(+)
 create mode 100644 .changeset/slow-keys-confess.md

diff --git a/.changeset/slow-keys-confess.md b/.changeset/slow-keys-confess.md
new file mode 100644
index 000000000..b69c86ae7
--- /dev/null
+++ b/.changeset/slow-keys-confess.md
@@ -0,0 +1,5 @@
+---
+'@gitbook/integration-googleanalytics': minor
+---
+
+Update Google Analytics integration configuration

From 65a7006118ec4c04a9fc989db187dd12f21d5397 Mon Sep 17 00:00:00 2001
From: Valentino Hudhra <v.hudhra@gmail.com>
Date: Fri, 11 Oct 2024 14:18:27 +0200
Subject: [PATCH 6/6] removed scopes

---
 integrations/googleanalytics/gitbook-manifest.yaml | 2 --
 1 file changed, 2 deletions(-)

diff --git a/integrations/googleanalytics/gitbook-manifest.yaml b/integrations/googleanalytics/gitbook-manifest.yaml
index c103347e6..01b9d99cd 100644
--- a/integrations/googleanalytics/gitbook-manifest.yaml
+++ b/integrations/googleanalytics/gitbook-manifest.yaml
@@ -13,8 +13,6 @@ script: ./src/index.ts
 # The following scope(s) are available only to GitBook Staff
 # See https://developer.gitbook.com/integrations/configurations#scopes
 scopes:
-    - space:script:inject
-    - space:script:cookies
     - site:script:inject
     - site:script:cookies
 contentSecurityPolicy: