The Wayback Machine - http://web.archive.org/web/20210817023216/https://github.com/bufferapp/app-shell
Skip to content
main
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

@bufferapp/app-shell

Buffer's Navigator for our front-end applications.

Usage

The Navigator is distributed as a self contained JS file, served from https://components.buffer.com/navigator. To ensure that all the Produt Apps are running the same version the file, is automatically updated every time a new commit is pushed into the main branch.

To consume the Navigator you will need to have a #navigator div right on top of the app #root div, and declare some style and the API_GATEWAY_URL.

">
<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- This style reset is required for all Apps to make sure we are not seeing any glitch on load -->
    <style type="text/css">
      #navigator {
        display: flex;
        flex-direction: column;
        min-height: 57px;
        max-height: 105px;
      }

      #root {
        background-color: #F5F5F5;
        display: flex;
        flex-direction: column;
        height: 100%;
        margin: 0;
        overflow: auto;
      }
    </style>
  </head>
  <body>
    <!-- This is the div used to render the Navigator -->
    <div id="navigator"></div>
    <!-- This is the div used to render the App -->
    <div id="root"></div>

    <!-- Define the URL for the APIs Gateway used by the App Shell -->
    <script type="text/javaScript">
      window.API_GATEWAY_URL= "https://graph.local.buffer.com";
    </script>
    <script src="https://components.buffer.com/navigator.js" async></script>
  </body>
</html>

Listening to Organization changes Events in a Product App

The Navigator is dispatching custom events on the window object. The key to those events are accessible in window.appshell.eventKeys. To react to Organization changes events in the App the easiest option is to listen to those events in a React Hook

  useEffect(() => {
    const { ORGANIZATION_EVENT_KEY } = window?.appshell?.eventKeys || {};

    function handleOrgChange({ detail }) {
      // Action will be present if we are dispatching an organization change event from the App
      if (!detail.action) {
        console.log(detail.OrganizationId);
        // handle the change in here, for example refetch the account query
      }
    }

    window.addEventListener(ORGANIZATION_EVENT_KEY, handleOrgChange);

    return function cleanup() {
      window.removeEventListener(ORGANIZATION_EVENT_KEY, handleOrgChange);
    };
  }, [window?.appshell]);
  // we are Listening for changes in the `window.appshell` object to make sure we are properly mounting even if the App is initialized before the Navigator.

Change the Organization from the AppShell

The Navigator is also exposing a series of actions in the window.appshell.actions object. One of those actions allow the Apps to trigger an Organization change.

  const { actions } = window?.appshell || {};
  actions.setCurrentOrganization(newOrganizationId);

Opening Modals from a Product App

  1. Hash in the URL: To use the plan selector or payment method modal with a hash in the URL, include #planSelector or #paymentMethod on the URL. This will open the desired modal on page load

  2. Using actions: To open the modal from a CTA, you can use actions:

const { actions, MODALS } = window?.appshell || {};

const ModalTesting = () => (
  <ModalContext.Consumer>
    {modal => (
      <button onClick={
        () => {actions.openModal(MODALS.paymentMethod, { cta: 'upgradePlan', ctaButton: 'renderModal' isUpgradeIntent: false })}
      }>Render Modal</button>
    )}
  </ModalContext.Consumer>
)

Using CTAs for tracking

In the method above, you'll notice that openModal accepts an object as a second paramater. The cta property is used for tracking. Use it when possible to allow for accurate tracking in Segment

Using intentions

Some places in our apps, the user's intent is to upgrade from Trial or from Free. For that, we only want to show 2 plan options (Essentials and Team) instead of 3. For these CTAs, pass in isUpgradeIntent : true as part of the second paramater in openModal:

<button onClick={
    () => {modal.openModal(MODALS.paymentMethod, { cta: 'upgradePlan', ctaButton: 'upgradePlan', isUpgradeIntent: true })}
  }>
  Upgrade
</button>

Contributing

  • Clone this repo and make your changes.
git clone git@github.com:bufferapp/app-shell.git
  • Ensure your editor is setup for ESLint and Prettier.
  • Make and test your changes locally (docs for this TBD, contact @hamstu in the meantime).
  • Make a pull request and have it reviewed and merged into main.
  • Now, from the main branch:
    • git pull
    • if you're not in packages/app-shell cd your way there
    • npm run build
    • Update CHANGELOG.md
    • npm version [patch|minor|major] - pick one depending on type of changes
    • git push origin main - push changes to GitHub
    • npm publish - publish the new version to NPM

Local development

Running the Navigator, in dev mode, against the local environment

  1. You will need to run the API Gateway and login services on buffer-dev: ./dev up api-gateway login
  2. Run yarn watch in this root directory
  3. visit https://appshell.local.buffer.com:3000

note: you will need to manually trust the certificate, if navigator.js is not loading you will also need to trust the certificate for that one, you can do that by visiting https://appshell.local.buffer.com:8085/main.js

Running the Navigator, in dev mode, against production

  1. go to https://login.buffer.com/ and login with your production account (impersonation will also work).
  2. from the root folder run yarn watch-production
  3. visit https://appshell.local.buffer.com:3000

Testing

We have two types of testing, unit tests, and UX tests. Unit tests use Jest , and can be run with yarn run test or yarn run test:watch. UX tests use Cypress, and can be run with yarn run test:ux or yarn run test:ux:live for Cypress live mode.

UX testing

UX testing are integration tests meant to ensure that the entire app behaves as expected in its entirety on all the various scenarios (Account types, billing states, …). Navigator tetst

All integrations tests are contained in the ./cypress/integration folder.

What to test

  • We want to tests complex core flows. For example: that a specifix CTA is properly showing for a subset of users, or that the billing flow can be fully executed.
  • do not test for a specific copy. This type of tests are quite brittle, on honestly not that useful. Instead, use static #ids to test for the presence of a specific component Ex:
    it('has an invite team CTA', () => {
      cy.get('#inviteTeamCTA')
        .should('exist')
    })
  • do not test the entire APIs flow (login, user fetching). While those tests can be very valuable (we have Synthetic tets in DD for those), they are outside of the scope of what we want to test for in a PR, also they are more expensive to run, and cumbersome to maintain. To avoid that you can leverage cy.intercept and fixtures to skip APIs request and return mocked data.

Ex:

    // All GraphQL request for the tests will return the account mock
    before(() => {
      cy.fixture('accountObFree').then(account => {
        cy.intercept('POST', 'https://graph.buffer.com/', {
          status: 200,
          body: account,
        })
        cy.visit('/')
      })
    })

Please read Cypress's Working with GraphQL guide for more details on mocking strategies.

Troubleshooting

If you spot any issues with your local setup, ping @msanroman any time!

TypeError: Cannot read property 'account' of undefined

https://cln.sh/fwcvzd

This is likely a local session issue. Heading to https://login.local.buffer.com and signing up should make it work!

I get a DNS_PROBE_FINISHED_NXDOMAIN when trying to load https://appshell.local.buffer.com:3000

This is likely that you don't have the domain on your /etc/hosts file. Updating your buffer-dev-config setup and running ./dev up again should fix it!

About

Buffer's shared App Shell for front-end applications

Resources

Packages

No packages published

Languages