Skip to content

Commit 24a7a97

Browse files
committed
Added view selector component and tested with actual GA account
1 parent e6d65f2 commit 24a7a97

9 files changed

Lines changed: 186 additions & 72 deletions

File tree

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# google-analytics-embed-react
2+
3+
Bringing the Google Analytics Embed Components for your ReactJS admin
4+
panels.
5+
6+
## Installation
7+
8+
```
9+
npm install google-analytics-embed-react
10+
```
11+

packages/google-analytics-embed-react/src/DataChart.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import * as React from 'react';
22
import GoogleAnalyticsContext, { GoogleAnalyticsContextContent } from './GoogleAnalyticsContext';
33

44
export type DataChartProps<T> = {
5-
query: any;
5+
/** Query */
6+
query: gapi.analytics.Query;
7+
/** Placeholder if a user not authenticated yet */
8+
children?: React.ReactNode;
9+
/** Styles to the container element */
10+
style?: React.CSSProperties;
611
} & T;
712

813
export default class DataChart<O> extends React.Component<DataChartProps<O>> {
@@ -20,8 +25,10 @@ export default class DataChart<O> extends React.Component<DataChartProps<O>> {
2025

2126
componentDidUpdate() {
2227
const [gaState, _] = this.context as GoogleAnalyticsContextContent;
23-
const { query, ...chartOptions } = this.props;
28+
const { query, children, style, ...chartOptions } = this.props;
29+
// Rendering the component only if a user authenticated
2430
if (gaState == 'AUTH_SUCCESS') {
31+
// Updating the existing chart with new options if already rendered
2532
if (this.googleDataChart) {
2633
this.googleDataChart.set({
2734
query,
@@ -32,6 +39,7 @@ export default class DataChart<O> extends React.Component<DataChartProps<O>> {
3239
}
3340
});
3441
} else {
42+
// Creating a chart if a chart instance not created
3543
this.googleDataChart = new gapi.analytics.googleCharts.DataChart({
3644
query,
3745
chart: {
@@ -43,11 +51,14 @@ export default class DataChart<O> extends React.Component<DataChartProps<O>> {
4351
this.googleDataChart.execute();
4452
}
4553
} else if (this.googleDataChart) {
54+
// Destroying the chart if the authentication method changed
4655
this.googleDataChart = null;
4756
}
4857
}
4958

5059
render(): React.ReactNode {
51-
return <div ref={this.elementRef}></div>;
60+
return (
61+
<div ref={this.elementRef}>{!this.googleDataChart ? this.props.children : undefined}</div>
62+
);
5263
}
5364
}

packages/google-analytics-embed-react/src/GoogleAnalyticsProvider.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export interface GoogleAnalyticsProviderProps {
3333
userInfoLabel?: string;
3434
}
3535

36+
/**
37+
* Wrap your application logics with this provider. This provider will
38+
* load the google platform script and notify the child components about
39+
* authentication events
40+
*/
3641
const GoogleAnalyticsProvider: React.FC<GoogleAnalyticsProviderProps> = (
3742
props: GoogleAnalyticsProviderProps
3843
): React.ReactElement => {
@@ -87,8 +92,6 @@ const GoogleAnalyticsProvider: React.FC<GoogleAnalyticsProviderProps> = (
8792
scopes: props.scopes,
8893
overwriteDefaultScopes: props.overwriteDefaultScopes
8994
});
90-
// TODO: Currently google not returning any error if the server access token is invalid.
91-
// Need to do a little research to handle it.
9295
setGaState('AUTH_SUCCESS');
9396
} else if (props.clientId && authButton) {
9497
// button authorization

packages/google-analytics-embed-react/src/SignInButton.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import * as React from 'react';
22
import GoogleAnalyticsContext, { GoogleAnalyticsContextContent } from './GoogleAnalyticsContext';
33

4+
/**
5+
* Put this component where you want to render the sign in button
6+
* when using the frontend authentication
7+
*/
48
class SignInButton extends React.Component {
59
static contextType = GoogleAnalyticsContext;
610
protected container: React.RefObject<HTMLDivElement>;
@@ -13,7 +17,7 @@ class SignInButton extends React.Component {
1317

1418
componentDidMount() {
1519
const [_, setAuthButton] = this.context as GoogleAnalyticsContextContent;
16-
setAuthButton(this.container.current as HTMLElement);
20+
setAuthButton(this.container.current as HTMLElement);
1721
}
1822

1923
render(): React.ReactNode {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import * as React from 'react';
2+
import GoogleAnalyticsContext, { GoogleAnalyticsContextContent } from './GoogleAnalyticsContext';
3+
4+
export interface ViewSelectorProps {
5+
/** Callback to fire after user changed the view */
6+
onChange?: (ids: string) => void;
7+
/** Placeholder to show until a user authenticating */
8+
children?: React.ReactNode;
9+
/** Styles for the container element */
10+
style?: React.CSSProperties;
11+
/** Class name for the container element */
12+
className?: string;
13+
}
14+
15+
/**
16+
* ViewSelector component will allow users to select their GA views.
17+
* onChange method will fire after a user changed the view
18+
*/
19+
export default class ViewSelector extends React.Component<ViewSelectorProps> {
20+
public static contextType = GoogleAnalyticsContext;
21+
private elementRef: React.RefObject<HTMLDivElement>;
22+
private googleViewSelector: gapi.analytics.ViewSelector | null;
23+
24+
constructor(props: ViewSelectorProps) {
25+
super(props);
26+
27+
this.elementRef = React.createRef();
28+
this.googleViewSelector = null;
29+
}
30+
31+
componentDidUpdate() {
32+
const [gaState, _] = this.context as GoogleAnalyticsContextContent;
33+
if (gaState == 'AUTH_SUCCESS') {
34+
if (!this.googleViewSelector) {
35+
this.googleViewSelector = new gapi.analytics.ViewSelector({
36+
container: this.elementRef.current as HTMLElement
37+
});
38+
this.googleViewSelector.execute();
39+
if (this.props.onChange) {
40+
this.googleViewSelector.on('change', this.props.onChange);
41+
}
42+
}
43+
} else if (this.googleViewSelector) {
44+
this.googleViewSelector = null;
45+
}
46+
}
47+
48+
render(): React.ReactNode {
49+
return (
50+
<div style={this.props.style} className={this.props.className} ref={this.elementRef}>
51+
{!this.googleViewSelector ? this.props.children : undefined}
52+
</div>
53+
);
54+
}
55+
}
Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,43 @@
1-
export { default as GoogleAnalyticsProvider, GoogleAnalyticsProviderProps } from './GoogleAnalyticsProvider';
1+
export {
2+
default as GoogleAnalyticsProvider,
3+
GoogleAnalyticsProviderProps
4+
} from './GoogleAnalyticsProvider';
25
export { default as SignInButton } from './SignInButton';
3-
import {default as DataChart, DataChartProps} from './DataChart';
4-
export {DataChartProps} from "./DataChart";
6+
import { default as DataChart, DataChartProps } from './DataChart';
7+
export { DataChartProps } from './DataChart';
8+
export { default as ViewSelector, ViewSelectorProps } from './ViewSelector';
59

610
export type LineChartOptions = google.visualization.LineChartOptions;
711
export class GoogleAnalyticsLineChart extends DataChart<LineChartOptions> {
8-
constructor(props: DataChartProps<LineChartOptions>) {
9-
super('LINE', props);
10-
}
12+
constructor(props: DataChartProps<LineChartOptions>) {
13+
super('LINE', props);
14+
}
1115
}
1216

1317
export type BarChartOptions = google.visualization.BarChartOptions;
1418
export class GoogleAnalyticsBarChart extends DataChart<BarChartOptions> {
15-
constructor(props: DataChartProps<BarChartOptions>) {
16-
super('BAR', props);
17-
}
19+
constructor(props: DataChartProps<BarChartOptions>) {
20+
super('BAR', props);
21+
}
1822
}
1923

2024
export type ColumnChartOptions = google.visualization.ColumnChartOptions;
2125
export class GoogleAnalyticsColumnChart extends DataChart<ColumnChartOptions> {
22-
constructor(props: DataChartProps<ColumnChartOptions>) {
23-
super('COLUMN', props);
24-
}
26+
constructor(props: DataChartProps<ColumnChartOptions>) {
27+
super('COLUMN', props);
28+
}
2529
}
2630

2731
export type GeoChartOptions = google.visualization.GeoChartOptions;
2832
export class GoogleAnalyticsGeoChart extends DataChart<GeoChartOptions> {
29-
constructor(props: DataChartProps<GeoChartOptions>) {
30-
super('GEO', props);
31-
}
33+
constructor(props: DataChartProps<GeoChartOptions>) {
34+
super('GEO', props);
35+
}
3236
}
3337

3438
export type TableOptions = google.visualization.TableOptions;
3539
export class GoogleAnalyticsTable extends DataChart<TableOptions> {
36-
constructor(props: DataChartProps<TableOptions>) {
37-
super('TABLE', props);
38-
}
40+
constructor(props: DataChartProps<TableOptions>) {
41+
super('TABLE', props);
42+
}
3943
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# google-analytics-embed-types
2+
3+
Type definitions for Google Analytics Embed Components
4+
5+
## Installation
6+
7+
```
8+
npm install --save-dev google-analytics-embed-types
9+
```
10+
11+
This package will exposing the `gapi.analytics` namespace for your
12+
projects. If you like to use this namespace as a global type, Import it
13+
in `tsconfig.json` file using `compilerOptions.types` property. This
14+
package will also exposing types from
15+
[@types/google.visualization](https://www.npmjs.com/package/@types/google.visualization)
16+
in some methods.
Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
declare namespace gapi.analytics {
2-
export interface Response {}
2+
export interface Response {
3+
[x: string]: any;
4+
}
35

46
export interface AuthOptions {
57
clientid?: string;
@@ -23,35 +25,20 @@ declare namespace gapi.analytics {
2325
static getAuthResponse(): Response;
2426
static getUserProfile(): Response;
2527

26-
static on(
27-
event: "signIn" | "signOut" | "needsAuthorization",
28-
callback: () => void
29-
): void;
30-
static on(event: "error", callback: (response: Response) => void): void;
31-
static once(
32-
event: "signIn" | "signOut" | "needsAuthorization",
33-
callback: () => void
34-
): void;
35-
static once(event: "error", callback: (response: Response) => void): void;
28+
static on(event: 'signIn' | 'signOut' | 'needsAuthorization', callback: () => void): void;
29+
static on(event: 'error', callback: (response: Response) => void): void;
30+
static once(event: 'signIn' | 'signOut' | 'needsAuthorization', callback: () => void): void;
31+
static once(event: 'error', callback: (response: Response) => void): void;
3632
static off(
37-
event: "signIn" | "signOut" | "needsAuthorization" | "error",
33+
event: 'signIn' | 'signOut' | 'needsAuthorization' | 'error',
3834
handler: Function
3935
): void;
4036

41-
on(
42-
event: "signIn" | "signOut" | "needsAuthorization",
43-
callback: () => void
44-
): void;
45-
on(event: "error", callback: (response: Response) => void): void;
46-
once(
47-
event: "signIn" | "signOut" | "needsAuthorization",
48-
callback: () => void
49-
): void;
50-
once(event: "error", callback: (response: Response) => void): void;
51-
off(
52-
event: "signIn" | "signOut" | "needsAuthorization" | "error",
53-
handler: Function
54-
): void;
37+
on(event: 'signIn' | 'signOut' | 'needsAuthorization', callback: () => void): void;
38+
on(event: 'error', callback: (response: Response) => void): void;
39+
once(event: 'signIn' | 'signOut' | 'needsAuthorization', callback: () => void): void;
40+
once(event: 'error', callback: (response: Response) => void): void;
41+
off(event: 'signIn' | 'signOut' | 'needsAuthorization' | 'error', handler: Function): void;
5542
}
5643

5744
export function ready(callback: () => void): void;
@@ -63,18 +50,39 @@ declare namespace gapi.analytics {
6350
execute(): void;
6451
}
6552

66-
type Query = Record<string, string>;
53+
export interface Query {
54+
ids: string;
55+
'start-date': string;
56+
'end-date': string;
57+
metrics: string;
58+
dimensions?: string;
59+
sort?: string;
60+
filters?: string;
61+
segment?: string;
62+
samplingLevel?: 'DEFAULT' | 'FASTER' | 'HIGHER_PRECISION';
63+
'include-empty-rows'?: boolean;
64+
'start-index'?: number;
65+
'max-results'?: number;
66+
output?: string;
67+
fields?: string;
68+
prettyPrint?: string;
69+
userIp?: string;
70+
quotaUser?: string;
71+
access_token?: string;
72+
callback?: string;
73+
key?: string;
74+
}
6775

6876
export namespace report {
6977
export interface DataOptions {
7078
query: Query;
7179
}
7280

7381
export class Data extends Component<DataOptions> {
74-
on(event: "success" | "error", cb: (res: Response) => void): void;
75-
once(event: "success" | "error", cb: (res: Response) => void): void;
76-
off(event: "success" | "error", handler: Function): void;
77-
emit(event: "success" | "error", res: Response): void;
82+
on(event: 'success' | 'error', cb: (res: Response) => void): void;
83+
once(event: 'success' | 'error', cb: (res: Response) => void): void;
84+
off(event: 'success' | 'error', handler: Function): void;
85+
emit(event: 'success' | 'error', res: Response): void;
7886
}
7987
}
8088

@@ -87,11 +95,11 @@ declare namespace gapi.analytics {
8795
export interface DataChartOptions {
8896
query: Query;
8997
chart:
90-
| ChartOptions<"LINE", google.visualization.LineChartOptions>
91-
| ChartOptions<"COLUMN", google.visualization.ColumnChartOptions>
92-
| ChartOptions<"BAR", google.visualization.BarChartOptions>
93-
| ChartOptions<"TABLE", google.visualization.TableOptions>
94-
| ChartOptions<"GEO", google.visualization.GeoChartOptions>;
98+
| ChartOptions<'LINE', google.visualization.LineChartOptions>
99+
| ChartOptions<'COLUMN', google.visualization.ColumnChartOptions>
100+
| ChartOptions<'BAR', google.visualization.BarChartOptions>
101+
| ChartOptions<'TABLE', google.visualization.TableOptions>
102+
| ChartOptions<'GEO', google.visualization.GeoChartOptions>;
95103
}
96104

97105
export interface DataChartSuccessResult {
@@ -102,13 +110,13 @@ declare namespace gapi.analytics {
102110
}
103111

104112
export class DataChart extends Component<DataChartOptions> {
105-
on(event: "success", cb: (res: DataChartSuccessResult) => void): void;
106-
on(event: "error", cb: (res: Response) => void): void;
107-
once(event: "success", cb: (res: DataChartSuccessResult) => void): void;
108-
once(event: "error", cb: (res: Response) => void): void;
109-
off(event: "success" | "error", handler: Function): void;
110-
emit(event: "success", res: DataChartSuccessResult): void;
111-
emit(event: "error", res: Response): void;
113+
on(event: 'success', cb: (res: DataChartSuccessResult) => void): void;
114+
on(event: 'error', cb: (res: Response) => void): void;
115+
once(event: 'success', cb: (res: DataChartSuccessResult) => void): void;
116+
once(event: 'error', cb: (res: Response) => void): void;
117+
off(event: 'success' | 'error', handler: Function): void;
118+
emit(event: 'success', res: DataChartSuccessResult): void;
119+
emit(event: 'error', res: Response): void;
112120
}
113121
}
114122

@@ -118,9 +126,9 @@ declare namespace gapi.analytics {
118126

119127
export class ViewSelector extends Component<ViewSelectorOptions> {
120128
ids: string;
121-
on(event: "change", cb: (ids: string) => void): void;
122-
once(event: "change", cb: (ids: string) => void): void;
123-
off(event: "change", handler: Function): void;
124-
emit(event: "change", ids: string): void;
129+
on(event: 'change', cb: (ids: string) => void): void;
130+
once(event: 'change', cb: (ids: string) => void): void;
131+
off(event: 'change', handler: Function): void;
132+
emit(event: 'change', ids: string): void;
125133
}
126134
}

0 commit comments

Comments
 (0)