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

feat: building google analytics plugin #37

Closed
wants to merge 4 commits into from

Conversation

AceTheCreator
Copy link
Member

Description

  • ...
  • ...
  • ...

Related issue(s)

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Welcome to AsyncAPI. Thanks a lot for creating your first pull request. Please check out our contributors guide and the instructions about a basic recommended setup useful for opening a pull request.

Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out this issue.

@AceTheCreator
Copy link
Member Author

@fmvilas Please kindly check out this draft PR and help me out 🙏

Copy link
Member

@fmvilas fmvilas left a comment

Choose a reason for hiding this comment

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

Overall looks great! I think you got the idea 🙌 I left some comments on how to improve it but this is mostly done.

PS: Sorry for the delay. Next time please ping me again so I don't forget 😅

Comment on lines 6 to 10
clientEvents.on = eventEmitter.on.bind(eventEmitter);
clientEvents.once = eventEmitter.once.bind(eventEmitter);
clientEvents.emit = (eventName, eventPayload, ...args) => {
eventEmitter.emit(eventName, eventPayload, ...args);
};
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we can just do this:

Suggested change
clientEvents.on = eventEmitter.on.bind(eventEmitter);
clientEvents.once = eventEmitter.once.bind(eventEmitter);
clientEvents.emit = (eventName, eventPayload, ...args) => {
eventEmitter.emit(eventName, eventPayload, ...args);
};
module.exports = eventEmitter;

yarn-error.log Outdated
@@ -0,0 +1,112 @@
Arguments:
Copy link
Member

Choose a reason for hiding this comment

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

I think you can get rid of this file 😄 Also, add it to .gitignore so it doesn't happen again.

const googleAnalytics = module.exports;

googleAnalytics.init = (window) => {
ReactGA.initialize('UA-000000-01');
Copy link
Member

Choose a reason for hiding this comment

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

I wonder how can we make this id configurable without exposing server-side secrets into the client code 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

What if the id can be passed as an extra arg when emitting the event on page render. @fmvilas what do you think?

@AceTheCreator
Copy link
Member Author

PS: Sorry for the delay. Next time please ping me again so I don't forget

Hi @fmvilas. I'll do that

@sonarqubecloud
Copy link

Kudos, SonarCloud Quality Gate passed!

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

@magicmatatjahu
Copy link
Member

magicmatatjahu commented May 18, 2021

@AceTheCreator I copy your comment from issue :)

Description

We used to have Google Analytics hardcoded (see #13). However, some people may not want to use it so it's better if we make it a plugin instead.

Implementation suggestion

  1. Go to src/pages/_app.js file and make it trigger a new kind of event on the render function. For instance, something like a page:render event. Make sure this event is only triggered on the client-side render.
  2. Make the plugin subscribe to the event and trigger an initialize and pageview call when a new event comes in.

Hints

The code for triggering/subscribing to events is on src/lib/events.js. However, this library was created for the backend and it imports the config lib, which may contain secrets. If we use the events lib on the frontend, these secrets may be exposed on the browser. Watch out!

@fmvilas when I subscribe to the event from a different file and I trigger it in the app.js render it doesn't work. For instance, when I call event names from the app.js to check the list of subscribed events, it returns an empty array. Except I call both the subscribe and trigger event within the page render function in the app.js
@fmvilas please check out my updated draft PR

The problem is that you don't have any triggers in the client side due to fact that writing code in render function under typeof window !== undefined you execute code only on client side, it means that binded events to registry are missed during rendering page on the server. NextJS works in this way that prepares data for component (getInitialProps function), then these data is transformed to JSON and attached to page. NextJS render page on server side, using of course getInitialProps function and then if user go to page React on client side hydrate page using these attached JSON data. Your case is hard, because you try emit event from registry binded on server side and then this data, on client side, is lost. So we must think how to "attach" events from external packages to client side and also how to import external plugins for client side, because at the moment it only works for server side (code for that is in plugins.js file).

@AceTheCreator
Copy link
Member Author

The problem is that you don't have any triggers in the client side due to fact that writing code in render function under typeof window !== undefined you execute code only on client side, it means that binded events to registry are missed during rendering page on the server. NextJS works in this way that prepares data for component (getInitialProps function), then these data is transformed to JSON and attached to page. NextJS render page on server side, using of course getInitialProps function and then if user go to page React on client side hydrate page using these attached JSON data. Your case is hard, because you try emit event from registry binded on server side and then this data, on client side, is lost. So we must think how to "attach" events from external packages to client side and also how to import external plugins for client side, because at the moment it only works for server side (code for that is in plugins.js file).

@magicmatatjahu what do you think will be the best approach in tackling this problem?

Comment on lines +1 to +17
import React from 'react';
import googleAnalytics from '@asyncapistudio/plugin-google-analytics';
import AppContext from '../contexts/AppContext'
import '../css/tailwind.css'
import clientEvents from '../lib/client-events';

class AsyncApiStudio extends App {
static async getInitialProps({ ctx }) {
if (!ctx || !ctx.req) return {}
function AsyncApiStudio({ Component, pageProps, context }) {
if (typeof window !== 'undefined') {
clientEvents.on('page:render', () => googleAnalytics.init(window));
clientEvents.emit('page:render');
}
return (
<AppContext.Provider value={context || {}}>
<Component {...pageProps} />
</AppContext.Provider>
);
}
Copy link
Member

Choose a reason for hiding this comment

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

@AceTheCreator I will answer here for you question.

As I wrote, the code:

    clientEvents.on('page:render', () => googleAnalytics.init(window));
    clientEvents.emit('page:render');

will be only executed on the client side and you will lost all events binded to clientEvents registry. As you wrote in previous comments, googleAnalytics.init only works when you bind this plugin as clientEvents.on('page:render', () => googleAnalytics.init(window)); in this file. So I see steps to do:

  • load events from plugins to clientEvents registry. We must remember that they should be loaded on client side, so loading in this file is probably the best place, but:

    • to load events we use the file system. It doesn't exist on client side, so we must handle this problem on different way, I mean load events from external packages/plugins but without filesystem. We can use https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr to load dynamic modules on client side :) However there is an another problem, because we must know about names of external packages/plugins. So this is the hardest part. How to get the packages name, and then load events from them? If you don't understand, please see example:

      If we will use the dynamic import from nextJS, we will have something like:

      const DynamicPlugin = dynamic(
        () => import('@asyncapistudio/plugin-google-analytics'), // I didn't test it, so I don't know if this works, but should
        { ssr: false }
      )
      
      clientEvents.on('page:render', () => DynamicPlugin.init(window));
      ...

      So we have hardcoded name of the plugin in source file. How to get external plugins from config.plugins array? To be honest I don't know, because then we must to do:

      const pluginsName = ... // here we must load the names of plugins from `config.plugins` array, but we cannot use filesystem, because data from it will be lost in client side, maybe some logic in `getInitialProps` will help
      
      for (const pluginName of pluginsName) {
        const DynamicPlugin = dynamic(
          () => import(pluginName), // I didn't test it, so I don't know if this works, but should
          { ssr: false }
        );
        clientEvents.on('page:render', () => ... bind page:render event from plugin);
      }
  • Execute page:render event.

Let me know if you understand the problem. I really don't know how to solve it the easiest way, because we're trying to bind the filesystem to the client side which is hard to do, I can say it is impossible. I don't see even very complicated solution at the moment for this problem 😢

I will try to spend some time on this problem, but I don't give guarantee that I will be able to solve something.

Copy link
Member Author

Choose a reason for hiding this comment

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

@magicmatatjahu sorry for the late response 🙏 I was quite busy clearing all blockers that will interfere with GSOC.
I get your approach to solving this problem and I'll try working on it to see what I can come up with. Look out for a draft PR and good job man 👍

Copy link
Member

@magicmatatjahu magicmatatjahu Jun 10, 2021

Choose a reason for hiding this comment

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

@AceTheCreator Hi! No worries, we know that you also study :) It's an open source so we also know that people work in the free time :) Please also don't waste your time on this issue, because it's really hard to make it in the good way. You should focus on your GSOC project :)

@github-actions
Copy link

This pull request has been automatically marked as stale because it has not had recent activity 😴
It will be closed in 60 days if no further activity occurs. To unstale this pull request, add a comment with detailed explanation.
Thank you for your contributions ❤️

@magicmatatjahu
Copy link
Member

@AceTheCreator I opened issue #80 Probably we will remove the NextJS and go with pure React app, so I will close PR, or do you have a different opinion on the subject?

@AceTheCreator
Copy link
Member Author

@AceTheCreator I opened issue #80 Probably we will remove the NextJS and go with pure React app, so I will close PR, or do you have a different opinion on the subject?

@magicmatatjahu It's fine. So will any suggested plugins be built separately?

@magicmatatjahu
Copy link
Member

magicmatatjahu commented Sep 22, 2021

@AceTheCreator The Studio will be SPA application, so there will be plugins - you can read proposal here. If you mean plugins for (server) API then we can also reuse my proposal - you can add comment/ask about plugins in the #86 issue, but to be more specific - the plugins will probably be written in monorepo and not as separate npm packages - but that can always be changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants