Kuma Documentation Release latest

Mozilla

October 30, 2020

Contents

1 Development 3

2 Getting started 5 2.1 Installation...... 5 2.2 Development...... 10 2.3 Troubleshooting...... 24 2.4 The Kuma test suite...... 26 2.5 Client-side testing...... 28 2.6 Feature toggles...... 30 2.7 Celery and async tasks...... 33 2.8 Search...... 35 2.9 Localization...... 36 2.10 CKEditor...... 39 2.11 Getting Data...... 41 2.12 Documentation...... 45 2.13 Docker...... 45 2.14 Deploying MDN...... 46 2.15 Rendering Documents...... 50 2.16 Building, collecting, and serving assets...... 63

i ii Kuma Documentation, Release latest

Kuma is the platform that powers MDN (developer.mozilla.org)

Contents 1 Kuma Documentation, Release latest

2 Contents CHAPTER 1

Development

Code https://github.com/mdn/kuma Issues P1 Bugs (to be fixed in current or next sprint) P2 Bugs (to be fixed in 180 days) All developer.mozilla.org bugs Pull Request Queues Dev Docs https://kuma.readthedocs.io/en/latest/installation.html CI Server https://travis-ci.com/mdn/kuma Forum https://discourse.mozilla.org/c/mdn Matrix #mdn room Servers What’s Deployed on MDN? https://developer.allizom.org/ (stage) https://developer.mozilla.org/ (prod)

3 Kuma Documentation, Release latest

4 Chapter 1. Development CHAPTER 2

Getting started

Want to help make MDN great? Our contribution guide lists some good first projects and offers direction on submitting code. Contents:

2.1 Installation

Kuma uses Docker for local development and integration testing, and we are transitioning to Docker containers for deployment as well. Current Status of Dockerization: • Kuma developers are using Docker for daily development and maintenance tasks. Staff developers primarily use Docker for Mac. Other staff members and contributors use Docker’s Ubuntu packages. • The development environment can use a lot of resources. On Docker for Mac, the environment runs well with 6 CPUs and 10 GB of memory dedicated to Docker. It can be run successfully on 2 CPUs and 2 GB of memory. • The Docker development environment is evolving rapidly, to address issues found during development and to move toward a containerized design. You may need to regularly reset your environment to get the current changes. • The Docker development environment doesn’t fully support a ‘production-like’ environment. For example, we don’t have a documented configuration for running with an SSL connection. • When the master branch is updated, the kuma_base image is refreshed and published to DockerHub. This image contains system packages and third-party libraries. • Our TravisCI builds include a target that build Docker containers and runs the tests inside. • Our Jenkins server builds and publishes Docker images, and runs integration tests using Docker. • We are documenting tips and tricks on the Troubleshooting page. • Feel free to ask for help on Matrix at #mdn or on discourse.

2.1.1 Docker setup

1. Install the Docker platform, following Docker’s instructions for your operating system, such as Docker for Mac for MacOS, or for your Linux distribution. Non-Linux users should increase Docker’s memory limits (Windows, macOS) to at least 4 GB, as the default of 2 GB is insufficient.

5 Kuma Documentation, Release latest

Linux users will also want to install Docker Compose and follow post-install instructions to confirm that the development user can run Docker commmands. To confirm that Docker is installed correctly, run: docker run hello-world

If you find any error using docker commands without sudo visit using docker as non-root user. 2. Clone the kuma Git repository, if you haven’t already: git clone --recursive https://github.com/mdn/kuma.git

If you think you might be submitting pull requests, consider forking the repository first, and then cloning your fork of it. 3. Ensure you are in the existing or newly cloned kuma working copy: cd kuma

4. Initialize and customize .env: cp.env-dist.dev.env .env # Or your favorite editor

Linux users should set the UID parameter in .env (i.e. change #UID=1000 to UID=1000) to avoid file permission issues when mixing docker-compose and docker commands. MacOS users do not need to change any of the defaults to get started. Note that there are settings in this file that can be useful when debug- ging, however. 5. Pull the Docker images and build the containers: docker-compose pull docker-compose build

(The build command is effectively a no-op at this point because the pull command just downloaded pre-built docker images.) 6. Start the containers in the background: docker-compose up -d

2.1.2 Load the sample database

Download the sample database with either of the following wget or curl (installed by default on macOS) commands: wget -N https://mdn-downloads.s3-us-west-2.amazonaws.com/mdn_sample_db.sql.gz curl -O https://mdn-downloads.s3-us-west-2.amazonaws.com/mdn_sample_db.sql.gz

Next, upload that sample database into the Kuma web container with: docker-compose exec web bash -c "zcat mdn_sample_db.sql.gz | ./manage.py dbshell"

(This command can be adjusted to restore from an uncompressed database, or directly from a mysqldump command.) Then run the following command: docker-compose exec web ./manage.py migrate

This will ensure the sample database is in sync with your version of Kuma.

6 Chapter 2. Getting started Kuma Documentation, Release latest

2.1.3 Compile locales

Localized string databases are included in their source form, and need to be compiled to their binary form: docker-compose exec web make localecompile

Dozens of lines of warnings will be printed: cd locale; ./compile-mo.sh . ./af/LC_MESSAGES/.po:2: warning: header field 'PO-Revision-Date' still has the initial default value ./af/LC_MESSAGES/django.po:2: warning: header field 'Last-Translator' still has the initial default value ... ./zu/LC_MESSAGES/.po:2: warning: header field 'PO-Revision-Date' still has the initial default value ./zu/LC_MESSAGES/javascript.po:2: warning: header field 'Last-Translator' still has the initial default value

Warnings are OK, and will be fixed as translators update the strings on Pontoon. If there is an error, the output will end with the error, such as: ./az/LC_MESSAGES/django.po:263: 'msgid' and 'msgstr' entries do not both end with '\n' msgfmt: found 1 fatal error

These need to be fixed by a Kuma developer. Notify them in the #mdn Matrix room or open a bug. You can continue with installation, but non-English locales will not be localized.

2.1.4 Generate static assets

Static assets such as CSS and JS are included in source form, and need to be compiled to their final form: docker-compose exec web make build-static

A few thousand lines will be printed, like: ## Generating JavaScript translation catalogs ## processing language en_US processing language af processing language ar ... ## Compiling (Sass), collecting, and building static files ## Copying '/app/kuma/static/img/embed/promos/survey.svg' Copying '/app/kuma/static/styles/components/home/column-callout.scss' Copying '/app/build/locale/jsi18n/fy-NL/javascript.js' ... Post-processed 'build/styles/editor-locale-ar.' as 'build/styles/editor-locale-ar.css' Post-processed 'build/styles/locale-ln.css' as 'build/styles/locale-ln.css' Post-processed 'build/styles/editor-locale-pt-BR.css' as 'build/styles/editor-locale-pt-BR.css' .... 1870 static files copied to '/app/static', 125 post-processed.

2.1.5 Visit the homepage

Open the homepage at http://localhost.org:8000 . You’ve installed Kuma!

2.1.6 Create an admin user

Many Kuma settings require access to the Django admin, including configuring social login. It is useful to create an admin account with password access for local development.

2.1. Installation 7 Kuma Documentation, Release latest

If you want to create a new admin account, use createsuperuser: docker-compose exec web ./manage.py createsuperuser

This will prompt you for a username, email address (a fake address like [email protected] will work), and a password. If your database has an existing account that you want to use, run the management command. Replace YOUR_USERNAME with your username and YOUR_PASSWORD with your password: docker-compose run --rm web ./manage.py ihavepower YOUR_USERNAME \ --password YOUR_PASSWORD

With a password-enabled admin account, you can log into Django admin at http://localhost.org:8000/admin/login

2.1.7 Update the Sites section

1. After logging in to the Django admin (an alternative is using the login test-super with password test-password), scroll down to the Sites section. 2. Click on “Change”. 3. Click on the entry that says localhost:8000. 4. Change both the domain and display name from localhost:8000 to localhost.org:8000. 5. Click “Save”.

2.1.8 Enable GitHub/Google authentication (optional)

Since Google’s OAuth requires us to use a valid top-level-domain, we’re going to use http://localhost.org:8000 as an example for every URL here. To automate setting Django up for social auth you can run docker-compose exec web ./manage.py configure_social_auth and follow its steps (and ignore the rest of this section). If you want to do it manually, follow these steps: To enable GitHub authentication, you’ll need to register an OAuth application on GitHub, with settings like: • Application name: MDN Development for (). • Homepage URL: http://localhost.org:8000/. • Application description: My own GitHub app for MDN! • Authorization callback URL: http://localhost.org:8000/users/github/login/callback/. To enable Google authentication, you’ll need to first create an API project on Google. After that we’ll need to configure credentials for that project with settings like: • Name: MDN Development for (). • Authorized JavaScript origins: http://localhost.org:8000 • Authorized redirect URIs: http://localhost.org:8000/users/google/login/callback/ As an admin user, add a django-allauth social app for both GitHub and Google do the following: • Provider: GitHub/Google. • Name: MDN Development.

8 Chapter 2. Getting started Kuma Documentation, Release latest

• Client id: . • Secret key: . • Sites: Move locahost:8000 from “Available sites” to “Chosen sites”. locahost:8000 needs to either have ID 1 or SITE_ID=1 has to be set in .env to its actual ID. You’ll also need to set DOMAIN=localhost.org (no port!) there. Your hosts file should contain the following lines: 127.0.0.1 localhost demos localhost.org .localhost.org 255.255.255.255 broadcasthost ::1 localhost demos localhost.org wiki.localhost.org

Now you can sign in with GitHub. To associate your password-only admin account with GitHub: 1. Login with your password at http://localhost.org:8000/admin/login. 2. Go to the Homepage at https://developer.mozilla.org/en-US/. 3. Click your username at the top to view your profile. 4. Click Edit to edit your profile. 5. Under My Profiles, click Use your GitHub account to sign in. To create a new account with GitHub, use the regular “Sign in” widget at the top of any page. With social accounts are enabled, you can disable the admin password in the Django shell: docker-compose exec web ./manage.py shell_plus >>> me = User.objects.get(username='admin_username') >>> me.set_unusable_password() >>> me.save() >>> exit()

2.1.9 Enable Stripe payments (optional)

1. Go to https://dashboard.stripe.com and create a Stripe account (if you don’t have one already). 2. Go to https://dashboard.stripe.com/apikeys and copy both the publishable and secret key into your .env file. The respective config keys are STRIPE_PUBLIC_KEY and STRIPE_SECRET_KEY. 3. Go to https://dashboard.stripe.com/test/subscriptions/products and create a new product and plan. 4. Once created copy the plan ID and also put it into .env as STRIPE_PLAN_ID. Unless you set a custom ID it should start with plan_. If you’re using Stripe in testing mode you can also get test numbers from this site: https://stripe.com/docs/testing#cards Testing Stripe’s hooks locally requires setting up a tunneling service, like ngrok (https://ngrok.com). You should then set CUSTOM_WEBHOOK_HOSTNAME to the hostname you get from your tunneling service, e.g. for ngrok it might be https://203ebfab.ngrok.io After kuma has started you will have a webhook configured in stripe. You can view it on Stripe’s dashboard: https://dashboard.stripe.com/test/webhooks Note that with the free tier a restart of ngrok gives you a new hostname, so you’ll have to change the config again and restart the server (or manually change the webhook in Stripe’s dashboard).

2.1. Installation 9 Kuma Documentation, Release latest

2.1.10 Enable Sendinblue email integration

1. Create a Sendinblue account over at https://www.sendinblue.com (you can skip a lot of the profile set-up, look for skip in the upper right). 2. Get your v3 API key at https://account.sendinblue.com/advanced/api 3. Create a list at https://my.sendinblue.com/lists/new/parent_id/1 4. Add the sendinblue config keys to your .env, the keynames are SENDINBLUE_API_KEY and SENDINBLUE_LIST_ID

2.1.11 Interact with the Docker containers

The current directory is mounted as the /app folder in the web and worker containers. Changes made to your local directory are usually reflected in the running containers. To force the issue, the containers for specified services can be restarted: docker-compose restart web worker

You can connect to a running container to run commands. For example, you can open an interactive shell in the web container: docker-compose exec web /bin/bash make bash # Same command, less typing

To view the logs generated by a container: docker-compose logs web

To continuously view logs from all containers: docker-compose logs -f

To stop the containers: docker-compose stop

If you have made changes to the .env or /etc/hosts file, it’s a good idea to run: docker-compose stop docker-compose up

For further information, see the Docker documentation, such as the Docker Overview and the documentation for your operating system. You can try Docker’s guided tutorials, and apply what you’ve learned on the Kuma Docker environment.

2.2 Development

2.2.1 Basic Docker usage

Edit files as usual on your host machine; the current directory is mounted via Docker host mounting at /app within various Kuma containers. Useful docker sub-commands:

10 Chapter 2. Getting started Kuma Documentation, Release latest

docker-compose exec web bash # Start an interactive shell docker-compose logs web # View logs from the web container docker-compose logs -f # Continuously view logs from all containers docker-compose restart web # Force a container to reload docker-compose stop # Shutdown the containers docker-compose up -d # Start the containers docker-compose rm # Destroy the containers

There are make shortcuts on the host for frequent commands, such as: make up # docker-compose up -d make bash # docker-compose exec web bash make shell_plus # docker-compose exec web ./manage.py shell_plus

Run all commands in this doc in the web service container after make bash.

2.2.2 Running Kuma

When the Docker container environment is started (make up or similar), all of the services are also started. The development instance is available at http://localhost:8000.

2.2.3 Running the tests

One way to confirm that everything is working, or to pinpoint what is broken, is to run the test suite.

Django tests

Run the Django test suite: make test

For more information, see the test documentation.

Functional tests

To run the functional tests, see Client-side Testing.

Kumascript tests

If you’re changing Kumascript, be sure to run its tests too. See https://github.com/mdn/kumascript.

2.2.4 Front-end development

Assets are processed in several steps by Django and django-pipeline: • Front-end localization libraries and string catalogs are generated based on gettext calls in Javascript files. • Sass source files are compiled to plain CSS with node-sass. • Assets are collected to the static/ folder. • CSS, JS, and image assets are included in templates using the {% stylesheet %}, {% javascript %}, and {% static %} Jinja2 macros.

2.2. Development 11 Kuma Documentation, Release latest

In production mode (DEBUG=False), there is additional processing. See Generating production assets for more information. To rebuild the front-end localization libraries: make compilejsi18n

To rebuild CSS, JS, and image assets: make collectstatic

To do both: make build-static

Compiling JS on the host system with webpack

For a quicker iteration cycle while developing the frontend app you can run: npm i npm run webpack:dev

This watches the react frontend and rebuilds both the web and SSR bundle when changes occur. It rebuilds only what has changed and also restarts the SSR server. It serves React in development mode which yields more explicit warnings and allows you to use tools such as the React DevTools. You can also run it in production mode: npm run webpack:prod

Style guide and linters

There is an evolving style guide at https://mdn.github.io/mdn-fiori/, sourced from https://github.com/mdn/mdn-fiori. Some of the style guidelines are enforced by linters. To run stylelint on all .scss files: npm run stylelint

To run eslint on .js files: npm run eslint

Bundle Analyze

To get an idea about the size distribution of our react codebase you can run: npm run webpack:analyze

This will open an interactively explorable report in your browser.

Add a React page

Let’s say we are adding a new Example page to the dashboards section on Kuma. We’d like the pathname to be /dashboards/example/. First we need to set up a few things in Django:

12 Chapter 2. Getting started Kuma Documentation, Release latest

1. In kuma/dashboards/jinja2/dashboards/, create a new Jinja template that our view will render called example.html. Paste the following code in example.html to get started: {% extends "react_base.html" %}

{% block title %}{{ page_title(_('Example Page')) }}{% endblock %}

{% block content %}This works!{% endblock %}

{# This overrides the Jinja footer, since we will be using the React footer instead. Without this line, two footers will render. #} {% block footer %}{% endblock %}

2. In kuma/dashboards/views.py, render the Jinja template we just created: def example(request): return render(request,"dashboards/example.html")

3. In kuma/dashboards/urls.py, add a new url that will render our view. This code will change slightly depending on your url and file structure: # Import the view

# (This next line most likely will already be there, if not, add it to the top of the file.) from. import views

...

re_path(r"^example/$", views.example, name="dashboards.example"),

4. We are done with the Django part! In your browser, go to http://wiki.localhost.org:8000/en- US/dashboards/example/ and you should see a page that says “This works!” 5. Now we can set up the React part. In kuma/dashboards/jinja2/dashboards/example.html, we can remove the “This works!” text and add the following code between {% block title %} and {% block content %}: {# Pass `True` for initial data if you do not have any data to fetch. Otherwise, change `True` to `None` or whatever your initial data will be. #} {% block document_head %} {{ render_react("SPA", request.LANGUAGE_CODE, request.get_full_path(), True)|safe }} {% endblock %}

6. So your kuma/dashboards/jinja2/dashboards/example.html file should now look something like this: {% extends "react_base.html" %}

{% block title %}{{ page_title(_('Example Page')) }}{% endblock %}

{# Pass `True` for initial data if you do not have any data to fetch (i.e. static page). #} {# Otherwise, change `True` to `None` or whatever your initial data will be. #} {% block document_head %} {{ render_react("SPA", request.LANGUAGE_CODE, request.get_full_path(), True)|safe }} {% endblock %}

{% block content %} {% endblock %}

{# This overrides the Jinja footer, since we will be using the React footer instead. Without this line, two footers will render. #} {% block footer %}{% endblock %}

2.2. Development 13 Kuma Documentation, Release latest

7. In kuma/javascript/src, if there is not a kuma/javascript/dashboards folder already, add a folder called dashboards. 8. In kuma/javascript/src/dashboards, create a file called example.jsx. Add the following lines as a starter: // @flow import * as React from 'react';

import { gettext } from '../l10n.js'; import A11yNav from '../a11y/a11y-nav.jsx'; import Header from '../header/header.jsx'; import Footer from '../footer.jsx'; import Route from '../route.js';

type ExampleRouteParams = { locale: string };

export default function ExamplePage() { return ( <>

{gettext('Hello!')}