Skip to content

Commit

Permalink
feat(scully): Add GoogleAnalytics render plugin. (scullyio#705)
Browse files Browse the repository at this point in the history
* feat(scully): Add GoogleAnalytics render plugin.
* chore(lib): add config for analytics and revert logrocket prod-config
* feat(scully): Add GoogleAnalytics render plugin.
* chore(lib): add config for analytics and revert logrocket prod-config
* fix(lib): add proper readme
* fix(monorepo): fix typo
* test(docsWeb): update snapshot
Co-authored-by: jorgeucano <[email protected]>
Co-authored-by: GuzmanPI <[email protected]>
Co-authored-by: sanderelias <[email protected]>
  • Loading branch information
GuzmanPI authored Jul 15, 2020
1 parent 3e68794 commit b8a7ee0
Show file tree
Hide file tree
Showing 30 changed files with 598 additions and 461 deletions.
5 changes: 1 addition & 4 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ module.exports = {
'ts-jest': {
tsConfig: './tsconfig.spec.json',
stringifyContentPathRegex: '\\.html$',
astTransformers: [
'jest-preset-angular/build/InlineFilesTransformer',
'jest-preset-angular/build/StripStylesTransformer',
],
astTransformers: ['jest-preset-angular/build/InlineFilesTransformer', 'jest-preset-angular/build/StripStylesTransformer'],
},
},
};
60 changes: 11 additions & 49 deletions libs/ng-lib/src/lib/transfer-state/transfer-state.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,7 @@ import { DOCUMENT } from '@angular/common';
import { Inject, Injectable } from '@angular/core';
import { NavigationEnd, NavigationStart, Router } from '@angular/router';
import { BehaviorSubject, NEVER, Observable, of } from 'rxjs';
import {
catchError,
filter,
first,
map,
pluck,
shareReplay,
switchMap,
take,
takeWhile,
tap,
} from 'rxjs/operators';
import { catchError, filter, first, map, pluck, shareReplay, switchMap, take, takeWhile, tap } from 'rxjs/operators';
import { fetchHttp } from '../utils/fetchHttp';
import { isScullyGenerated, isScullyRunning } from '../utils/isScully';
import { mergePaths } from '../utils/merge-paths';
Expand Down Expand Up @@ -77,17 +66,10 @@ export class TransferStateService {
shareReplay(1)
);

constructor(
@Inject(DOCUMENT) private document: Document,
private router: Router
) {}
constructor(@Inject(DOCUMENT) private document: Document, private router: Router) {}

startMonitoring() {
if (
window &&
window['ScullyIO-injected'] &&
window['ScullyIO-injected'].inlineStateOnly
) {
if (window && window['ScullyIO-injected'] && window['ScullyIO-injected'].inlineStateOnly) {
this.inlineOnly = true;
}
this.setupEnvForTransferState();
Expand All @@ -106,10 +88,7 @@ export class TransferStateService {
} else if (isScullyGenerated()) {
// On the client AFTER scully rendered it
this.initialUrl = window.location.pathname || '__no_NO_no__';
this.initialUrl =
this.initialUrl !== '/' && this.initialUrl.endsWith('/')
? this.initialUrl.slice(0, -1)
: this.initialUrl;
this.initialUrl = this.initialUrl !== '/' && this.initialUrl.endsWith('/') ? this.initialUrl.slice(0, -1) : this.initialUrl;
/** set the initial state */
this.stateBS.next((window && window[SCULLY_SCRIPT_ID]) || {});
}
Expand Down Expand Up @@ -150,11 +129,7 @@ export class TransferStateService {
* Checks also if there is actually an value in the state.
*/
stateKeyHasValue(name: string) {
return (
this.stateBS.value &&
this.stateBS.value.hasOwnProperty(name) &&
this.stateBS.value[name] != null
);
return this.stateBS.value && this.stateBS.value.hasOwnProperty(name) && this.stateBS.value[name] != null;
}

/**
Expand All @@ -170,9 +145,7 @@ export class TransferStateService {

private saveState(newState) {
if (isScullyRunning()) {
this.script.textContent = `window['${SCULLY_SCRIPT_ID}']=${SCULLY_STATE_START}${JSON.stringify(
newState
)}${SCULLY_STATE_END}`;
this.script.textContent = `window['${SCULLY_SCRIPT_ID}']=${SCULLY_STATE_START}${JSON.stringify(newState)}${SCULLY_STATE_END}`;
}
}

Expand Down Expand Up @@ -201,10 +174,7 @@ export class TransferStateService {
* @param name state key
* @param originalState an observable which yields the desired data
*/
useScullyTransferState<T>(
name: string,
originalState: Observable<T>
): Observable<T> {
useScullyTransferState<T>(name: string, originalState: Observable<T>): Observable<T> {
if (isScullyGenerated()) {
return this.getState(name);
}
Expand All @@ -213,8 +183,7 @@ export class TransferStateService {

private async fetchTransferState(): Promise<void> {
/** helper to read the part before the first slash (ignores leading slash) */
const base = (url: string) =>
url.split('/').filter((part) => part.trim() !== '')[0];
const base = (url: string) => url.split('/').filter((part) => part.trim() !== '')[0];
/** put this in the next event cycle so the correct route can be read */
await new Promise((r) => setTimeout(r, 0));
/** get the current url */
Expand All @@ -231,9 +200,7 @@ export class TransferStateService {
/** keep updating till we move to another route */
takeWhile((url) => base(url) === this.currentBaseUrl),
// Get the next route's data from the the index or data file
switchMap((url) =>
this.inlineOnly ? this.readFromIndex(url) : this.readFromJson(url)
),
switchMap((url) => (this.inlineOnly ? this.readFromIndex(url) : this.readFromJson(url))),
catchError((e) => {
// TODO: come up with better error text.
/** the developer needs to know, but its not fatal, so just return an empty state */
Expand All @@ -259,13 +226,8 @@ export class TransferStateService {
}

private readFromIndex(url): Promise<object> {
return fetchHttp<string>(
dropPreSlash(mergePaths(url, '/index.html')),
'text'
).then((html: string) => {
const newStateStr = html
.split(SCULLY_STATE_START)[1]
.split(SCULLY_STATE_END)[0];
return fetchHttp<string>(dropPreSlash(mergePaths(url, '/index.html')), 'text').then((html: string) => {
const newStateStr = html.split(SCULLY_STATE_START)[1].split(SCULLY_STATE_END)[0];
return JSON.parse(newStateStr);
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,18 @@
import {
HandledRoute,
registerPlugin,
setMyConfig,
getMyConfig,
log,
yellow,
} from '@scullyio/scully';
import { HandledRoute, registerPlugin, setMyConfig, getMyConfig, log, yellow } from '@scullyio/scully';

export const baseHrefRewrite = Symbol('baseHrefRewrite');

const baseHrefRewritePlugin = async (
html: string,
route: HandledRoute
): Promise<string> => {
const baseHrefRewritePlugin = async (html: string, route: HandledRoute): Promise<string> => {
let { href } = getMyConfig(baseHrefRewritePlugin);
/** if there is a predicate and it returns falsy, don't do anything */
if (
route.config?.baseHrefPredicate &&
!route.config?.baseHrefPredicate(html, route)
) {
if (route.config?.baseHrefPredicate && !route.config?.baseHrefPredicate(html, route)) {
return html;
}
if (route.config?.baseHref && typeof route.config?.baseHref === 'string') {
href = route.config.baseHref;
}

log(
`Rewritten 'base href' to ${yellow(href)}, for route: ${yellow(
route.route
)}`
);
log(`Rewritten 'base href' to ${yellow(href)}, for route: ${yellow(route.route)}`);
if (!html.toLowerCase().includes('<base')) {
/** there is none, just add one. */
return html.replace(/<\/head[\s>]/i, `<base href="${href}"></head>`);
Expand Down
32 changes: 8 additions & 24 deletions libs/plugins/extra/src/lib/plugins-extra.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
import {
routeSplit,
registerPlugin,
HandledRoute,
yellow,
} from '@scullyio/scully';
import { routeSplit, registerPlugin, HandledRoute, yellow } from '@scullyio/scully';

/**
* This plugin replaces the parameter with a counter from 0 to the numberOfPages
* in the config.
* @param route
* @param options
*/
export const extraRoutesPlugin = async (
route,
options
): Promise<Partial<HandledRoute>[]> => {
export const extraRoutesPlugin = async (route, options): Promise<Partial<HandledRoute>[]> => {
/**
* routeSplit takes the route and returns a object.
* The createPath property in there is a function that takes the
Expand All @@ -24,12 +16,10 @@ export const extraRoutesPlugin = async (
const { createPath } = routeSplit(route);
if (options.numberOfPages) {
/** we are going to add numberOfPages handledRoutes, with the number as parameter */
return Array.from({ length: options.numberOfPages }, (_v, k) => k).map(
(n) => ({
route: createPath(n.toString()),
title: `page number ${n}`,
})
);
return Array.from({ length: options.numberOfPages }, (_v, k) => k).map((n) => ({
route: createPath(n.toString()),
title: `page number ${n}`,
}));
}
/** just in case */
return [];
Expand All @@ -39,17 +29,11 @@ const validator = async (options) => {
const errors = [];

if (options.numberOfPages === undefined) {
errors.push(
`Extraroutes parameter ${yellow(
'numberOfPages'
)} is missing from the config`
);
errors.push(`Extraroutes parameter ${yellow('numberOfPages')} is missing from the config`);
}

if (options.numberOfPages && typeof options.numberOfPages !== 'number') {
errors.push(
`extraroutesPlugin plugin numberOfPages should be a number, not a ${typeof options.numberOfPages}`
);
errors.push(`extraroutesPlugin plugin numberOfPages should be a number, not a ${typeof options.numberOfPages}`);
}

return errors;
Expand Down
1 change: 1 addition & 0 deletions libs/plugins/google-analytics/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "extends": "../../../.eslintrc", "rules": {}, "ignorePatterns": ["!**/*"] }
54 changes: 54 additions & 0 deletions libs/plugins/google-analytics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# plugins-google-analytics

This library was generated with [Nx](https://nx.dev).

## Running unit tests

# Run `ng test plugins-google-analytics` to execute the unit tests via [Jest](https://jestjs.io).

# Google Analytics

## Description

This plugin allows the usage of Google Analytics via Global Site Tag.

## Type

Render Plugin

## Usage

In the application's scully.<your-app>.config.ts:

1. Configure the plugin:

The plugin's configuration receives an object like this `{globalSiteTag: string}` where
the `globalSiteTag` is the `gtag` provided by Google Analytics.

2. Make a default post render array and add the plugin to it.

3. Set the default post renders in Scully config.

e.g.
`./scully.<your-app>.config.ts`

```typescript
import { setPluginConfig, ScullyConfig, prod } from '@scullyio/scully';
import { GoogleAnalytics } from '@scullyio/plugins/google-analytics';

const defaultPostRenderers = [];

if (prod) {
setPluginConfig(GoogleAnalytics, { globalSiteTag: 'UA-#########-1' });
defaultPostRenderers.push(GoogleAnalytics);
}
export const config: ScullyConfig = {
defaultPostRenderers,
routes: {
'/': {
type: 'contentFolder',
postRenderers: [...defaultPostRenderers],
},
},
};
```
9 changes: 9 additions & 0 deletions libs/plugins/google-analytics/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
name: 'plugins-google-analytics',
preset: '../../../jest.config.js',
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
coverageDirectory: '../../../coverage/libs/plugins/google-analytics',
};
10 changes: 10 additions & 0 deletions libs/plugins/google-analytics/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@scullyio/plugins-google-analytics",
"version": "0.0.1",
"author": "Israel Guzman",
"repository": {
"type": "GIT",
"url": "https://github.com/scullyio/scully/tree/main/libs/plugins/google-analytics"
},
"license": "MIT"
}
1 change: 1 addition & 0 deletions libs/plugins/google-analytics/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/plugins-google-analytics';
27 changes: 27 additions & 0 deletions libs/plugins/google-analytics/src/lib/plugins-google-analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { registerPlugin, getMyConfig } from '@scullyio/scully';

export const GoogleAnalytics = 'googleAnalytics';

export const googleAnalyticsPlugin = async (html: string): Promise<string> => {
const googleAnalyticsConfig = getMyConfig(googleAnalyticsPlugin);

if (!googleAnalyticsConfig) {
throw new Error('googleAnalytics plugin missing Global Site Tag');
}
const siteTag: string = googleAnalyticsConfig['globalSiteTag'];

const googleAnalyticsScript = `
<script async src="https://www.googletagmanager.com/gtag/js?id=${siteTag}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${siteTag}');
</script>`;

return html.replace(/<\/head/i, `${googleAnalyticsScript}</head`);
};

const validator = async () => [];

registerPlugin('render', GoogleAnalytics, googleAnalyticsPlugin, validator);
7 changes: 7 additions & 0 deletions libs/plugins/google-analytics/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"types": ["node", "jest"]
},
"include": ["**/*.ts"]
}
12 changes: 12 additions & 0 deletions libs/plugins/google-analytics/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "../../../dist/out-tsc",
"declaration": true,
"rootDir": "./src",
"types": ["node"]
},
"exclude": ["**/*.spec.ts"],
"include": ["**/*.ts"]
}
15 changes: 15 additions & 0 deletions libs/plugins/google-analytics/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"**/*.spec.ts",
"**/*.spec.tsx",
"**/*.spec.js",
"**/*.spec.jsx",
"**/*.d.ts"
]
}
Loading

0 comments on commit b8a7ee0

Please sign in to comment.