Search code examples
javascriptgitlabwiki

Javascript/html in gitlab wiki page?


I'm afraid I don't really have an idea how to do this - I want to use variables in a Gitlab Wiki page. is this possible at all using html? Can I run scripts on a page?

For example, this question on using a javascript variable in html - would something like this be possible?

For my specific example, I want a page with a table where the first column is numbers, and I want to take the largest value and display it at the top of the page. So for example the table might have rows like:

1   X
22  Y
15  Z

So at the top of the page I would display "largest number is 22". Does this make sense?


Solution

  • You cannot have a script in your Wikipage, since they sanitize the HTML (See what they allow here). To do what you want, you could generate that page in Gitlab-CI as part of your project's deploy pipeline, using whatever rendering engine and putting that variable in there, and update it automatically using Gitlab's Wiki APIs.

    How to

    I created a demo NodeJS project here, which, when I push to the master branch, auto-generates the Wiki pages. You can look at the code to see how it works.

    This example app exposes a function to get a list of fruit and quantities in stock. We'll automatically add that data in the Wiki.

    Step 1 - Create page templates

    You can add templates to your project for your Wiki pages. In my example, I used MustacheJS. And I put everything in a wiki folder (see folder structure at the end of step 5). Your template can look something like this:

    wiki/templates/home.mst

    # Welcome to the supermarket
    
    The biggest quantity we have in stock is for **{{topProduct.label}}**,
    with a total of **{{topProduct.stock}}**!
    
    |   Fruit   |   Quantity   |
    |-----------|--------------|
    {{#inventory}}
    | {{label}} | {{stock}} |
    {{/inventory}}
    

    In this example, the data will come from the project code itself.

    Step 2 - Build your pages

    Note: The scripts I wrote as a demo use axios to make requests to the Gitlab API, mustache to render the pages, and qs to format the data as a query string before posting it to Gitlab. You can use other ones or install them as dev dependencies: npm install --save-dev axios mustache qs

    Create a js file which will get the data from your app, and render the templates into a build directory. Something like this:

    wiki/build.js

    const fs = require('fs');
    const Mustache = require('mustache');
    const myApp = require('../src/index.js');
    const inventory = myApp.getInventory();
    
    // Get the Mustache template
    const homeTemplate = fs.readFileSync(__dirname + '/templates/home.mst', 'utf-8');
    
    // Get the fruit with the highest quantity
    const topProduct = inventory.reduce((acc, curr) => {
      if (acc === null || curr.stock > acc.stock) {
        return curr;
      } else {
        return acc;
      }
    }, null);
    
    // Render the page using your variables
    const homeContent = Mustache.render(homeTemplate, { inventory, topProduct });
    
    // Write the file in a build directory
    const buildDir = __dirname + '/../build';
    if (!fs.existsSync(buildDir)) {
      fs.mkdirSync(buildDir);
    }
    
    fs.writeFileSync(buildDir + '/home.md', homeContent);
    

    And in your package.json, add a command to run that script:

    "scripts": {
        // ...
        "wiki:build": "node wiki/build.js"
      }
    

    Step 3 - Deploy your Wiki pages

    Create a script which will upload the pages to your Wiki. This may work as-is without much modification, if you're also using NodeJS.

    wiki/deploy.js

    const fs = require('fs');
    const Axios = require('axios');
    const qs = require('qs');
    
    const config = {
      gitlabBaseUrl: 'https://gitlab.com', // Update this if you're on a private Gitlab
      projectId: process.env.CI_PROJECT_ID, // Provided by Gitlab-CI
      privateToken: process.env.WIKI_DEPLOY_TOKEN, // Added through Gitlab interface
      buildDir: __dirname + '/../build'
    };
    
    const axios = Axios.create({
      baseURL: config.gitlabBaseUrl,
      headers: { 'Private-Token': config.privateToken, Accept: 'application/json' }
    });
    
    (async function deploy() {
      const existingPages = await getExistingWikiPages();
      const updatedPages = getUpdatedPages();
      // Pages which existed but are no longer in the build (obsolete)
      const pagesToDelete = existingPages.filter(p1 => updatedPages.every(p2 => p2.slug !== p1.slug));
      // Pages which didn't exist before
      const pagesToCreate = updatedPages.filter(p1 => existingPages.every(p2 => p2.slug !== p1.slug));
      // Pages which already exist
      const pagesToUpdate = updatedPages.filter(p1 => existingPages.some(p2 => p2.slug === p1.slug));
    
      console.log(
        `Found ${pagesToDelete.length} pages to delete, ${pagesToCreate.length} pages to create, ${pagesToUpdate.length} pages to update.`
      );
      for (let page of pagesToDelete) {
        await deletePage(page);
      }
      for (let page of pagesToCreate) {
        await createPage(page);
      }
      for (let page of pagesToUpdate) {
        await updatePage(page);
      }
      console.log('Deploy complete!');
    })();
    
    function getExistingWikiPages() {
      return axios.get(`/api/v4/projects/${config.projectId}/wikis`).then(res => res.data);
    }
    
    function getUpdatedPages() {
      const files = fs.readdirSync(config.buildDir);
      return files.map(file => {
        const name = file // Remove the file extension
          .split('.')
          .slice(0, -1)
          .join('.');
        return {
          format: 'markdown', // You could make this depend on the file extension
          slug: name,
          title: name,
          content: fs.readFileSync(`${config.buildDir}/${file}`, 'utf-8')
        };
      });
    }
    
    function deletePage(page) {
      console.log(`Deleting ${page.slug}...`);
      return axios.delete(`/api/v4/projects/${config.projectId}/wikis/${page.slug}`);
    }
    
    function createPage(page) {
      console.log(`Creating ${page.slug}...`);
      return axios.post(`/api/v4/projects/${config.projectId}/wikis`, qs.stringify(page));
    }
    
    function updatePage(page) {
      console.log(`Updating ${page.slug}...`);
      return axios.put(`/api/v4/projects/${config.projectId}/wikis/${page.slug}`, qs.stringify(page));
    }
    

    In the config at the top, you need to specify which URL your Gitlab is using. CI_PROJECT_ID will be provided by Gitlab-CI itself as an environment variable. WIKI_DEPLOY_TOKEN, however, will not. Set it up in step 4.

    And in your package.json, add a command to run that script:

    "scripts": {
        // ...
        "wiki:build": "node wiki/build.js",
        "wiki:deploy": "node wiki/deploy.js"
      }
    

    Note: This example will delete obsolete pages, and update or create new ones depending on files it finds in the build folder and the ones the Wiki already contains. If you want to have attachments as well (images), you'll need to make use of this API also.

    Step 4 - Setup a private token WIKI_DEPLOY_TOKEN

    For this, you'll need to click on your profile picture at the top right corner > Settings. Then in the left menu, Access Tokens, and create a token with the api scope. The name does not matter. Copy this token now, since it will only be shown once.

    Then, go to your project. In the menu on the left, click Settings > CI/CD. Expand the Variables section, and use the previously copied token to create a variable called WIKI_DEPLOY_TOKEN, make it Masked so that it does not appear in any logs, and Save variables:

    enter image description here

    This will make that token available only in your pipelines, as an environment variable.

    Step 5 - Create your pipeline

    If you don't already have a pipeline, all you need to do is create a .gitlab-ci.yml file at the root of your project. Declare a generate_wiki stage:

    .gitlab-ci.yml

    stages:
      # - tests
      # - deploy
      # ...
      - generate_wiki
    
    generate_wiki:
      image: node:10
      stage: generate_wiki
      script:
        - npm install
        - npm run wiki:build  # build the wiki in a directory
        - npm run wiki:deploy # update it in Gitlab
      only:
        - master # Only when merging or pushing to master branch
    
    
    # ... rest of your pipeline ...
    

    As you can see, we use the commands wiki:build and wiki:deploy declared in steps 2 and 3.

    Now, your project structure should look something like this:

    /
    ├───src
    │    └── index.js
    ├───wiki
    │    ├── templates
    │    │    └── home.mst
    │    ├── build.js
    │    └── deploy.js
    ├── .gitlab-ci.yml
    └── package.json
    

    Step 6 - Push to master, and enjoy the magic

    After pushing, if everything went right, you can click on CI/CD in the left menu, and you should see your pipeline running:

    enter image description here

    If you click on the little circle, you should see the logs:

    enter image description here

    And if you go to your Wiki pages, they should be up to date, automagically:

    enter image description here